/**
 * 模型库管理器
 * 负责模型库的显示、拖拽、上传和管理
 * 从 app.js 剥离出来的独立模块
 */

import * as THREE from 'three';
import { GLTFLoader } from '../../lib/three/examples/jsm/loaders/GLTFLoader.js';
import { FBXLoader } from '../../lib/three/examples/jsm/loaders/FBXLoader.js';
import projectManager from '../project/projectManager.js';
import projectAPI from '../project/projectAPI.js';
import { modelAnalyzer } from './modelAnalyzer.js';
import modelCacheManager from './ModelCacheManager.js';
import { getTwoStageUploadManager } from './TwoStageUploadManager.js';
import { fixStringEncoding } from '../utils/encodingFixer.js';
import { logModelLibraryInfo } from '../utils/modelLogHelper.js';
import loadingProgressManager from '../utils/loadingProgress.js';

// ============ 模型库全局变量 ============
let dragPreview = null;
let dragPreviewMesh = null;
let currentDragType = null;
let dragNameLabel = null;
let currentDragModelData = null; // 保存拖拽的模型数据（路径、ID等）
let isPreviewModelLoaded = false; // 标记预览模型是否已加载完成

// 暴露到全局，供场景树节点使用
window.currentDragModelData = null;

// 上传相关变量
let uploadQueue = [];
let uploadXHR = null;
let isUploading = false;

// ============ 模型库初始化 ============

/**
 * 初始化模型库功能
 * @param {Object} sceneManager - 场景管理器实例
 */
export function initializeModelLibrary(sceneManager) {
    // 保存场景管理器引用
    window._sceneManager = sceneManager;
    
    // 标签页切换 - 模型库和节点库的tab切换
    const modelLibraryPanel = document.getElementById('model-library-panel-content');
    if (modelLibraryPanel) {
        modelLibraryPanel.querySelectorAll('.model-library-tabs .tab-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                const tabName = this.dataset.tab;
                switchModelLibraryTab(tabName);
            });
        });
    }
    
    // 初始化搜索功能
    const searchInput = document.getElementById('model-search-input');
    const searchClearBtn = document.getElementById('model-search-clear');
    
    if (searchInput) {
        searchInput.addEventListener('input', () => {
            applyFilters();
        });
    }
    
    if (searchClearBtn) {
        searchClearBtn.addEventListener('click', () => {
            if (searchInput) {
                searchInput.value = '';
                applyFilters();
            }
        });
    }
    
    // 初始化标签筛选清除按钮
    const clearTagFilterBtn = document.getElementById('clear-tag-filter');
    if (clearTagFilterBtn) {
        clearTagFilterBtn.addEventListener('click', () => {
            clearTagFilter();
        });
    }
    
    // 初始化节点库搜索功能
    const nodeSearchInput = document.getElementById('node-search-input');
    const nodeSearchClearBtn = document.getElementById('node-search-clear');
    
    if (nodeSearchInput) {
        nodeSearchInput.addEventListener('input', () => {
            applyNodeFilters();
        });
    }
    
    if (nodeSearchClearBtn) {
        nodeSearchClearBtn.addEventListener('click', () => {
            if (nodeSearchInput) {
                nodeSearchInput.value = '';
                applyNodeFilters();
            }
        });
    }
    
    // 初始化节点库标签筛选清除按钮
    const clearNodeTagFilterBtn = document.getElementById('clear-node-tag-filter');
    if (clearNodeTagFilterBtn) {
        clearNodeTagFilterBtn.addEventListener('click', () => {
            clearNodeTagFilter();
        });
    }
    
    // 初始化文件上传监听器
    initFileUploadListener();
    
    // 🎯 监听模型缓存状态变化，实时更新UI
    modelCacheManager.addListener((modelId, status, extra) => {
        updateModelCardStatus(modelId, status, extra);
    });
}

/**
 * 清理模型库资源（在退出项目时调用）
 */
export function cleanupModelLibrary() {
    // 取消所有正在进行的下载任务
    if (modelCacheManager.hasActiveDownloads()) {
        modelCacheManager.cancelAllDownloads();
    }
    
    // 清空模型缓存（可选）
    // modelCacheManager.clearAll();
}

// 暴露到全局，供其他模块调用
window.cleanupModelLibrary = cleanupModelLibrary;

// ============ 模型库面板控制 ============

/**
 * 打开模型库面板（已废弃，由 RightToolbarManager 管理）
 */
window.openModelLibrary = async function() {
    console.warn('⚠️ openModelLibrary 已废弃，由 RightToolbarManager 管理');
    // 兼容性保留，不执行任何操作
};

/**
 * 关闭模型库面板（已废弃，由 RightToolbarManager 管理）
 */
window.closeModelLibrary = function() {
    console.warn('⚠️ closeModelLibrary 已废弃，由 RightToolbarManager 管理');
    // 兼容性保留，不执行任何操作
};

/**
 * 刷新模型库侧边栏（仅在打开时刷新）
 */
window.refreshModelLibraryPanel = async function() {
    const panel = document.getElementById('model-library-panel-content');
    if (panel && panel.classList.contains('show')) {
        await window.populateModelList();
    }
};

/**
 * 模型库Tab切换（模型库 / 节点库）
 */
function switchModelLibraryTab(tabName) {
    const modelLibraryPanel = document.getElementById('model-library-panel-content');
    if (!modelLibraryPanel) {
        console.error('❌ 未找到模型库面板');
        return;
    }
    
    // 更新按钮状态
    modelLibraryPanel.querySelectorAll('.model-library-tabs .tab-btn').forEach(btn => {
        if (btn.dataset.tab === tabName) {
            btn.classList.add('active');
        } else {
            btn.classList.remove('active');
        }
    });
    
    // 更新面板显示
    const modelLibraryTab = document.getElementById('model-library-tab');
    const nodeLibraryTab = document.getElementById('node-library-tab');
    
    if (tabName === 'model-library') {
        modelLibraryTab.classList.add('active');
        nodeLibraryTab.classList.remove('active');
    } else if (tabName === 'node-library') {
        modelLibraryTab.classList.remove('active');
        nodeLibraryTab.classList.add('active');
        // 每次打开节点库都刷新列表（确保数据最新）
        if (window.populateNodeList && typeof window.populateNodeList === 'function') {
            window.populateNodeList();
        }
    }
}

// ============ 几何体列表 ============

/**
 * 填充几何体列表
 * 已禁用 - 不再需要几何体放置功能
 */
// window.populateGeometryList = function() {
//     const geometryList = document.getElementById('geometry-list');
//     
//     const geometryLibrary = {
//         cube: { name: '立方体', icon: '🎲', color: 0x4CAF50 },
//         sphere: { name: '球体', icon: '⚪', color: 0x2196F3 },
//         cylinder: { name: '圆柱', icon: '🥫', color: 0xFF9800 },
//         cone: { name: '圆锥', icon: '🔺', color: 0xF44336 },
//         torus: { name: '圆环', icon: '🍩', color: 0x9C27B0 },
//         plane: { name: '平面', icon: '📄', color: 0x607D8B }
//     };
//     
//     geometryList.innerHTML = '';
//     
//     for (const [type, info] of Object.entries(geometryLibrary)) {
//         const item = document.createElement('div');
//         item.className = 'resource-item';
//         item.dataset.type = type;
//         item.dataset.icon = info.icon;
//         item.dataset.color = info.color;
//         item.innerHTML = `
//             <div class="resource-icon">${info.icon}</div>
//             <div class="resource-name">${info.name}</div>
//         `;
//         geometryList.appendChild(item);
//     }
// }

// ============ 3D模型列表 ============

/**
 * 填充3D模型列表
 */
window.populateModelList = async function() {
    const modelList = document.getElementById('model-list');
    
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            modelList.innerHTML = '<div class="panel-info">请先选择一个项目</div>';
            return;
        }
        
        // 加载项目配置中的并发下载数
        modelCacheManager.loadMaxConcurrentDownloadsFromProject(currentProject);
        
        // 显示加载状态
        modelList.innerHTML = '<div class="panel-info">加载中...</div>';
        
        // 从API获取模型列表
        const response = await fetch(`/api/projects/${currentProject.id}/models`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error(result.error || '获取模型列表失败');
        }
        
        const models = result.data || [];
        
        // 更新模型总数
        const modelCountEl = document.getElementById('model-count');
        if (modelCountEl) {
            modelCountEl.textContent = models.length;
        }
        
        if (models.length === 0) {
            modelList.innerHTML = '<div class="panel-info">暂无3D模型<br>点击 ⚙️ 管理按钮上传模型</div>';
            return;
        }
        
        // 清空列表
        modelList.innerHTML = '';
        
        // 存储所有模型数据供筛选和搜索使用
        window._allModels = models;
        
        // 渲染模型列表
        renderModelList(models);
        // 加载标签筛选列表
        await loadTagsFilter();
        
    } catch (error) {
        console.error('❌ 加载模型列表失败:', error);
        modelList.innerHTML = '<div class="panel-info" style="color: #ff6b6b;">加载失败<br>' + error.message + '</div>';
    }
};

/**
 * 填充节点库列表
 */
window.populateNodeList = async function() {
    const nodeList = document.getElementById('node-list');
    
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            nodeList.innerHTML = '<div class="panel-info">请先选择一个项目</div>';
            return;
        }
        
        // 显示加载状态
        nodeList.innerHTML = '<div class="panel-info">加载中...</div>';
        
        // 从API获取节点模型列表（使用特殊的type参数标识）
        const response = await fetch(`/api/projects/${currentProject.id}/models?type=node`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error(result.error || '获取节点模型列表失败');
        }
        
        const nodes = result.data || [];
        
        if (nodes.length === 0) {
            nodeList.innerHTML = '<div class="panel-info">暂无节点模型<br>点击上方按钮上传节点模型组</div>';
            return;
        }
        
        // 清空列表
        nodeList.innerHTML = '';
        
        // 存储所有节点数据供筛选和搜索使用
        window._allNodes = nodes;
        
        // 渲染节点列表
        renderNodeList(nodes);
        // 加载节点标签筛选列表
        await loadNodeTagsFilter();
        
    } catch (error) {
        console.error('❌ 加载节点模型列表失败:', error);
        nodeList.innerHTML = '<div class="panel-info" style="color: #ff6b6b;">加载失败<br>' + error.message + '</div>';
    }
}

/**
 * 渲染节点库列表
 */
function renderNodeList(nodes) {
    const nodeList = document.getElementById('node-list');
    
    if (!nodeList) {
        console.error('❌ 未找到节点列表元素');
        return;
    }
    
    if (nodes.length === 0) {
        nodeList.innerHTML = '<div class="panel-info">暂无符合条件的节点模型</div>';
        return;
    }
    
    // 清空列表
    nodeList.innerHTML = '';
    
    // 添加节点项（与模型列表类似的逻辑）
    nodes.forEach((node, index) => {
        const item = document.createElement('div');
        item.className = 'resource-item';
        item.dataset.type = 'node-model';
        item.dataset.modelId = node.id;
        item.dataset.modelPath = node.path;
        item.dataset.modelFormat = node.format;
        item.dataset.icon = getModelFormatIcon(node.format);
        
        // 检查模型加载状态
        const loadStatus = modelCacheManager.getLoadingStatus(node.id);
        const isLoaded = loadStatus === 'loaded';
        const isLoading = loadStatus === 'loading';
        
        // 根据加载状态添加CSS类
        if (isLoaded) {
            item.classList.add('model-loaded');
        } else if (isLoading) {
            item.classList.add('model-loading');
        } else {
            item.classList.add('model-unloaded');
        }
        
        // 构建标签HTML
        let tagsHTML = '';
        if (node.tags && node.tags.length > 0) {
            tagsHTML = '<div class="model-tags">';
            node.tags.forEach(tag => {
                tagsHTML += `<span class="model-tag">${tag}</span>`;
            });
            tagsHTML += '</div>';
        }
        
        // 获取文件大小
        const fileSize = node.size ? formatFileSize(node.size) : '';
        const fileSizeHTML = fileSize ? `<div class="model-size">${fileSize}</div>` : '';
        
        item.innerHTML = `
            <div class="resource-number">${index + 1}</div>
            <div class="resource-info">
                <div class="resource-name" title="${node.name}">${node.name}</div>
                ${fileSizeHTML}
                ${tagsHTML}
            </div>
        `;
        
        // 为未下载的节点添加下载按钮
        if (!isLoaded && !isLoading) {
            const downloadBtn = document.createElement('button');
            downloadBtn.className = 'model-download-btn';
            downloadBtn.textContent = '下载';
            downloadBtn.title = '下载到缓存';
            downloadBtn.onclick = (e) => {
                e.stopPropagation();
                startModelDownload(node.id);
            };
            item.appendChild(downloadBtn);
            
            // 未下载的节点禁止拖拽
            item.style.cursor = 'default';
            item.draggable = false;
        } else {
            // 已下载的节点可以拖拽
            item.style.cursor = 'pointer';
            item.draggable = false; // 使用自定义拖拽
        }
        
        // 添加右键菜单
        item.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            
            // 🎯 如果节点正在加载中，不显示右键菜单
            const loadStatus = modelCacheManager.getLoadingStatus(node.id);
            if (loadStatus === 'loading') {
                return;
            }
            
            showModelContextMenu(e, node);
        });
        
        nodeList.appendChild(item);
    });
}

/**
 * 加载节点标签筛选列表
 */
async function loadNodeTagsFilter() {
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) return;
        
        const response = await fetch(`/api/projects/${currentProject.id}/models/tags/all?type=node`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error(result.error || '获取节点标签列表失败');
        }
        
        const allTags = result.data || [];
        renderNodeTagsFilter(allTags);
        
    } catch (error) {
        console.error('❌ 加载节点标签筛选失败:', error);
    }
}

/**
 * 渲染节点标签筛选列表
 */
function renderNodeTagsFilter(allTags) {
    const container = document.getElementById('node-tags-filter-list');
    if (!container) return;
    
    if (allTags.length === 0) {
        container.innerHTML = '<div class="tags-filter-empty">暂无标签</div>';
        return;
    }
    
    container.innerHTML = allTags.map(tag => `
        <button class="tag-filter-btn" data-tag="${tag}" onclick="toggleNodeTagFilter('${tag}')">
            ${tag}
        </button>
    `).join('');
}

/**
 * 切换节点标签筛选
 */
let selectedNodeTags = [];

window.toggleNodeTagFilter = function(tag) {
    const index = selectedNodeTags.indexOf(tag);
    if (index > -1) {
        selectedNodeTags.splice(index, 1);
    } else {
        selectedNodeTags.push(tag);
    }
    
    // 更新按钮状态
    const buttons = document.querySelectorAll('#node-tags-filter-list .tag-filter-btn');
    buttons.forEach(btn => {
        if (selectedNodeTags.includes(btn.dataset.tag)) {
            btn.classList.add('active');
        } else {
            btn.classList.remove('active');
        }
    });
    
    // 应用筛选
    applyNodeFilters();
};

/**
 * 清除节点标签筛选
 */
function clearNodeTagFilter() {
    selectedNodeTags = [];
    
    // 更新按钮状态
    const buttons = document.querySelectorAll('#node-tags-filter-list .tag-filter-btn');
    buttons.forEach(btn => {
        btn.classList.remove('active');
    });
    
    // 应用筛选
    applyNodeFilters();
}

/**
 * 应用节点筛选（搜索+标签）
 */
function applyNodeFilters() {
    if (!window._allNodes) return;
    
    // 获取搜索关键词
    const searchInput = document.getElementById('node-search-input');
    const searchKeyword = searchInput ? searchInput.value.toLowerCase().trim() : '';
    
    // 过滤节点
    let filteredNodes = window._allNodes;
    
    // 搜索过滤
    if (searchKeyword) {
        filteredNodes = filteredNodes.filter(node => 
            node.name.toLowerCase().includes(searchKeyword)
        );
    }
    
    // 标签过滤
    if (selectedNodeTags.length > 0) {
        filteredNodes = filteredNodes.filter(node => {
            if (!node.tags || node.tags.length === 0) return false;
            return selectedNodeTags.every(tag => node.tags.includes(tag));
        });
    }
    
    // 渲染过滤后的节点列表
    renderNodeList(filteredNodes);
}

/**
 * 渲染模型列表
 */
function renderModelList(models) {
    const modelList = document.getElementById('model-list');
    
    if (!modelList) {
        console.error('❌ 未找到模型列表元素');
        return;
    }
    
    if (models.length === 0) {
        modelList.innerHTML = '<div class="panel-info">暂无符合条件的模型</div>';
            return;
        }
        
        // 清空列表
        modelList.innerHTML = '';
        
        // 添加模型项
        models.forEach((model, index) => {
            const item = document.createElement('div');
            item.className = 'resource-item';
            item.dataset.type = 'custom-model';
            item.dataset.modelId = model.id;
            item.dataset.modelPath = model.path;
        item.dataset.modelFormat = model.format;
            item.dataset.icon = getModelFormatIcon(model.format);
        
        // 🎯 检查模型加载状态
        const loadStatus = modelCacheManager.getLoadingStatus(model.id);
        const isLoaded = loadStatus === 'loaded';
        const isLoading = loadStatus === 'loading';
        
        // 根据加载状态添加CSS类
        if (isLoaded) {
            item.classList.add('model-loaded');
        } else if (isLoading) {
            item.classList.add('model-loading');
        } else {
            item.classList.add('model-unloaded');
        }
        
        // 构建标签HTML
        let tagsHTML = '';
        if (model.tags && model.tags.length > 0) {
            tagsHTML = '<div class="model-tags">';
            model.tags.forEach(tag => {
                tagsHTML += `<span class="model-tag">${tag}</span>`;
            });
            tagsHTML += '</div>';
        }
        
        // 🎯 获取文件大小
        const fileSize = model.size ? formatFileSize(model.size) : '';
        const fileSizeHTML = fileSize ? `<div class="model-size">${fileSize}</div>` : '';
        
        // 🎯 不再显示状态指示器，避免与下载按钮重叠
            
            item.innerHTML = `
                <div class="resource-number">${index + 1}</div>
            <div class="resource-info">
                <div class="resource-name" title="${model.name}">${model.name}</div>
                ${fileSizeHTML}
                ${tagsHTML}
            </div>
        `;
        
        // 🎯 为未下载的模型添加下载按钮
        if (!isLoaded && !isLoading) {
            const downloadBtn = document.createElement('button');
            downloadBtn.className = 'model-download-btn';
            downloadBtn.textContent = '下载';
            downloadBtn.title = '下载到缓存';
            downloadBtn.onclick = (e) => {
                e.stopPropagation();
                startModelDownload(model.id);
            };
            item.appendChild(downloadBtn);
            
            // 🎯 未下载的模型禁止拖拽
            item.style.cursor = 'default';
            item.draggable = false;
        } else {
            // 已下载的模型可以拖拽
            item.style.cursor = 'pointer'; // 使用pointer样式，不显示move
            item.draggable = false; // 不使用HTML5 draggable，使用自定义拖拽
        }
        
        // 添加右键菜单
        item.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            
            // 🎯 如果模型正在加载中，不显示右键菜单
            const loadStatus = modelCacheManager.getLoadingStatus(model.id);
            if (loadStatus === 'loading') {
                return;
            }
            
            showModelContextMenu(e, model);
        });
            
            modelList.appendChild(item);
        });
}

/**
 * 更新单个模型卡片的加载状态
 * @param {string} modelId - 模型ID
 * @param {string} status - 加载状态 ('loaded' | 'loading' | 'error' | 'unloaded' | 'downloading')
 * @param {Object} extra - 额外信息（如下载进度）
 */
function updateModelCardStatus(modelId, status, extra = {}) {
    // 🎯 同时查找模型库和节点库的容器
    const modelList = document.getElementById('model-list');
    const nodeList = document.getElementById('node-list');
    
    // 先在模型库中查找
    let modelCard = null;
    if (modelList) {
        modelCard = modelList.querySelector(`[data-model-id="${modelId}"]`);
    }
    
    // 如果在模型库中没找到，再在节点库中查找
    if (!modelCard && nodeList) {
        modelCard = nodeList.querySelector(`[data-model-id="${modelId}"]`);
    }
    
    // 如果两个库中都没找到，直接返回
    if (!modelCard) return;
    
    // 移除所有状态类
    modelCard.classList.remove('model-loaded', 'model-loading', 'model-unloaded', 'model-error', 'model-downloading');
    
    // 添加新状态类
    if (status === 'loaded') {
        modelCard.classList.add('model-loaded');
        // 已下载的模型可以拖拽
        modelCard.style.cursor = 'pointer';
        modelCard.draggable = false; // 使用自定义拖拽
    } else if (status === 'loading') {
        modelCard.classList.add('model-loading');
    } else if (status === 'downloading') {
        modelCard.classList.add('model-downloading');
    } else if (status === 'error') {
        modelCard.classList.add('model-error');
    } else {
        modelCard.classList.add('model-unloaded');
        // 未下载的模型禁止拖拽
        modelCard.style.cursor = 'default';
        modelCard.draggable = false;
    }
    
    // 🎯 不再显示状态指示器，只使用下载按钮来表示状态
    
    // 🎯 更新下载按钮状态
    updateDownloadButton(modelCard, status);
}

/**
 * 更新模型卡片上的下载按钮状态
 * @param {HTMLElement} modelCard - 模型卡片元素
 * @param {string} status - 加载状态
 */
function updateDownloadButton(modelCard, status) {
    let downloadBtn = modelCard.querySelector('.model-download-btn');
    
    if (status === 'loaded') {
        // 已下载，隐藏按钮
        if (downloadBtn) {
            downloadBtn.remove();
        }
    } else if (status === 'downloading') {
        // 下载中，只显示状态，不提供取消功能
        if (!downloadBtn) {
            downloadBtn = document.createElement('button');
            downloadBtn.className = 'model-download-btn downloading';
            modelCard.appendChild(downloadBtn);
        }
        downloadBtn.className = 'model-download-btn downloading';
        downloadBtn.textContent = '下载中...';
        downloadBtn.title = '下载中';
        downloadBtn.onclick = null; // 移除点击事件
        downloadBtn.style.cursor = 'default'; // 鼠标样式改为默认
        downloadBtn.disabled = true; // 禁用按钮
    } else if (status === 'unloaded' || status === 'error') {
        // 未下载或下载失败，显示下载按钮
        if (!downloadBtn) {
            downloadBtn = document.createElement('button');
            downloadBtn.className = 'model-download-btn';
            modelCard.appendChild(downloadBtn);
        }
        downloadBtn.className = 'model-download-btn';
        downloadBtn.textContent = '下载';
        downloadBtn.title = '下载到缓存';
        downloadBtn.onclick = (e) => {
            e.stopPropagation();
            const modelId = modelCard.dataset.modelId;
            startModelDownload(modelId);
        };
    }
}

// ============ 标签管理功能 ============

// 当前选中的标签筛选
let selectedTags = [];

/**
 * 加载标签筛选列表
 */
async function loadTagsFilter() {
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) return;
        
        const response = await fetch(`/api/projects/${currentProject.id}/models/tags/all`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error(result.error || '获取标签列表失败');
        }
        
        const allTags = result.data || [];
        renderTagsFilter(allTags);
        
    } catch (error) {
        console.error('❌ 加载标签筛选失败:', error);
    }
}

/**
 * 渲染标签筛选列表
 */
function renderTagsFilter(allTags) {
    const tagsFilterList = document.getElementById('tags-filter-list');
    if (!tagsFilterList) return;
    
    if (allTags.length === 0) {
        tagsFilterList.innerHTML = '<div class="tags-filter-list-empty">暂无标签</div>';
        return;
    }
    
    // 统计每个标签的使用次数
    const tagCounts = {};
    window._allModels.forEach(model => {
        if (model.tags && Array.isArray(model.tags)) {
            model.tags.forEach(tag => {
                tagCounts[tag] = (tagCounts[tag] || 0) + 1;
            });
        }
    });
    
    tagsFilterList.innerHTML = '';
    allTags.forEach(tag => {
        const count = tagCounts[tag] || 0;
        const tagItem = document.createElement('div');
        tagItem.className = 'tag-filter-item';
        if (selectedTags.includes(tag)) {
            tagItem.classList.add('active');
        }
        tagItem.innerHTML = `${tag}<span class="tag-count">(${count})</span>`;
        tagItem.addEventListener('click', () => toggleTagFilter(tag));
        tagsFilterList.appendChild(tagItem);
    });
}

/**
 * 切换标签筛选
 */
function toggleTagFilter(tag) {
    const index = selectedTags.indexOf(tag);
    if (index > -1) {
        selectedTags.splice(index, 1);
    } else {
        selectedTags.push(tag);
    }
    
    applyFilters();
}

/**
 * 清除标签筛选
 */
function clearTagFilter() {
    selectedTags = [];
    applyFilters();
}

/**
 * 应用筛选和搜索
 */
function applyFilters() {
    const searchInput = document.getElementById('model-search-input');
    const searchText = searchInput ? searchInput.value.trim().toLowerCase() : '';
    
    let filteredModels = window._allModels || [];
    
    // 应用标签筛选
    if (selectedTags.length > 0) {
        filteredModels = filteredModels.filter(model => {
            if (!model.tags || model.tags.length === 0) return false;
            // 模型必须包含所有选中的标签
            return selectedTags.every(tag => model.tags.includes(tag));
        });
    }
    
    // 应用名称搜索
    if (searchText) {
        filteredModels = filteredModels.filter(model => 
            model.name.toLowerCase().includes(searchText)
        );
    }
    
    // 重新渲染列表
    renderModelList(filteredModels);
    
    // 更新标签筛选UI
    const tagsFilterList = document.getElementById('tags-filter-list');
    if (tagsFilterList) {
        tagsFilterList.querySelectorAll('.tag-filter-item').forEach(item => {
            const tagText = item.textContent.replace(/\(\d+\)/, '').trim();
            if (selectedTags.includes(tagText)) {
                item.classList.add('active');
            } else {
                item.classList.remove('active');
            }
        });
    }
    
    // 更新搜索清除按钮
    updateSearchClearButton();
}

/**
 * 更新搜索清除按钮
 */
function updateSearchClearButton() {
    const searchInput = document.getElementById('model-search-input');
    const clearBtn = document.getElementById('model-search-clear');
    
    if (searchInput && clearBtn) {
        if (searchInput.value.trim()) {
            clearBtn.classList.add('visible');
        } else {
            clearBtn.classList.remove('visible');
        }
    }
}

/**
 * 显示模型右键菜单
 */
function showModelContextMenu(event, model) {
    // 移除已有的菜单
    const existingMenu = document.querySelector('.model-context-menu');
    if (existingMenu) {
        existingMenu.remove();
    }
    
    const menu = document.createElement('div');
    menu.className = 'model-context-menu';
    menu.style.position = 'fixed';
    menu.style.left = event.clientX + 'px';
    menu.style.top = event.clientY + 'px';
    menu.style.background = 'rgba(30, 30, 30, 0.95)';
    menu.style.backdropFilter = 'blur(10px)';
    menu.style.border = '1px solid rgba(102, 126, 234, 0.3)';
    menu.style.borderRadius = '6px';
    menu.style.padding = '5px';
    menu.style.zIndex = '10000';
    menu.style.minWidth = '150px';
    menu.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.5)';
    
    menu.innerHTML = `
        <div class="context-menu-item" data-action="edit-tags">
            <span>🏷️ 编辑标签</span>
        </div>
    `;
    
    // 点击菜单项
    menu.addEventListener('click', async (e) => {
        const item = e.target.closest('.context-menu-item');
        if (!item) return;
        
        const action = item.dataset.action;
        if (action === 'edit-tags') {
            await showEditTagsDialog(model);
        }
        
        menu.remove();
    });
    
    document.body.appendChild(menu);
    
    // 点击其他地方关闭菜单
    setTimeout(() => {
        document.addEventListener('click', function closeMenu() {
            menu.remove();
            document.removeEventListener('click', closeMenu);
        });
    }, 0);
}

/**
 * 显示编辑标签对话框
 */
async function showEditTagsDialog(model) {
    const currentTags = model.tags || [];
    
    const dialog = document.createElement('div');
    dialog.className = 'modal-overlay';
    dialog.style.position = 'fixed';
    dialog.style.top = '0';
    dialog.style.left = '0';
    dialog.style.right = '0';
    dialog.style.bottom = '0';
    dialog.style.background = 'rgba(0, 0, 0, 0.85)';
    dialog.style.backdropFilter = 'blur(8px)';
    dialog.style.display = 'flex';
    dialog.style.alignItems = 'center';
    dialog.style.justifyContent = 'center';
    dialog.style.zIndex = '999999';
    
    dialog.innerHTML = `
        <div class="modal-content" style="background: rgba(30, 30, 30, 0.95); backdrop-filter: blur(10px); border: 1px solid rgba(102, 126, 234, 0.3); border-radius: 12px; padding: 25px; min-width: 400px; max-width: 500px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);">
            <h3 style="color: #ffffff; margin: 0 0 20px 0; font-size: 18px;">编辑模型标签</h3>
            <div style="margin-bottom: 15px;">
                <div style="color: rgba(255, 255, 255, 0.8); margin-bottom: 8px; font-size: 14px;">模型: ${model.name}</div>
                <input type="text" id="tags-input" placeholder="输入标签，用逗号分隔" value="${currentTags.join(', ')}" style="width: 100%; padding: 10px; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; color: #ffffff; font-size: 14px;" />
                <div style="color: rgba(255, 255, 255, 0.5); margin-top: 8px; font-size: 12px;">提示：多个标签用逗号分隔，例如：设备,水泵,重要</div>
            </div>
            <div style="display: flex; gap: 10px; justify-content: flex-end;">
                <button id="cancel-btn" style="padding: 8px 16px; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; color: #ffffff; cursor: pointer; transition: all 0.2s;">取消</button>
                <button id="save-btn" style="padding: 8px 16px; background: rgba(102, 126, 234, 0.8); border: none; border-radius: 6px; color: #ffffff; cursor: pointer; transition: all 0.3s;">保存</button>
            </div>
        </div>
    `;
    
    document.body.appendChild(dialog);
    
    const tagsInput = dialog.querySelector('#tags-input');
    const cancelBtn = dialog.querySelector('#cancel-btn');
    const saveBtn = dialog.querySelector('#save-btn');
    
    // 聚焦输入框
    tagsInput.focus();
    tagsInput.select();
    
    // 取消按钮
    cancelBtn.addEventListener('click', () => {
        dialog.remove();
    });
    
    // 保存按钮
    saveBtn.addEventListener('click', async () => {
        const tagsText = tagsInput.value.trim();
        const tags = tagsText ? tagsText.split(',').map(t => t.trim()).filter(t => t) : [];
        
        try {
            const currentProject = projectManager.getCurrentProject();
            if (!currentProject) {
                throw new Error('未选择项目');
            }
            
            // 🎯 判断是模型还是节点，使用不同的API endpoint
            const isNode = model.type === 'node' || window._allNodes?.find(n => n.id === model.id);
            const modelType = isNode ? 'node' : 'model';
            
            const response = await fetch(`/api/projects/${currentProject.id}/models/${model.id}?type=${modelType}`, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ tags })
            });
            
            const result = await response.json();
            
            if (!result.success) {
                throw new Error(result.error || '保存标签失败');
            }
            dialog.remove();
            
            // 🎯 根据类型刷新对应的列表
            if (isNode) {
                await loadNodeLibrary();
            } else {
                await window.populateModelList();
            }
            
        } catch (error) {
            console.error('❌ 保存标签失败:', error);
            alert('保存标签失败: ' + error.message);
        }
    });
    
    // 点击背景关闭
    dialog.addEventListener('click', (e) => {
        if (e.target === dialog) {
            dialog.remove();
        }
    });
    
    // ESC 关闭
    const escHandler = (e) => {
        if (e.key === 'Escape') {
            dialog.remove();
            document.removeEventListener('keydown', escHandler);
        }
    };
    document.addEventListener('keydown', escHandler);
}

// ============ 拖拽功能 ============

// 拖拽状态变量
let isDragging = false;
let dragStartX = 0;
let dragStartY = 0;
let dragThreshold = 5; // 拖拽阈值（像素）
let pendingDragItem = null;

/**
 * 初始化拖拽监听器
 */
window.initDragListeners = function() {
    // const geometryList = document.getElementById('geometry-list'); // 已禁用几何体功能
    const modelList = document.getElementById('model-list');
    const nodeList = document.getElementById('node-list');
    
    // geometryList.addEventListener('mousedown', onDragStart); // 已禁用几何体功能
    if (modelList) {
        modelList.addEventListener('mousedown', onDragStart);
    }
    if (nodeList) {
        nodeList.addEventListener('mousedown', onDragStart);
    }
    document.addEventListener('mousemove', onDragMove);
    document.addEventListener('mouseup', onDragEnd);
}

async function onDragStart(e) {
    const item = e.target.closest('.resource-item');
    if (!item) return;
    
    const manager = window._sceneManager;
    const currentScene = manager.getCurrentScene();
    if (!currentScene || !currentScene.scene) {
        console.warn('⚠️ 当前没有激活的场景');
        return;
    }
    
    // 记录起始位置
    dragStartX = e.clientX;
    dragStartY = e.clientY;
    isDragging = false;
    
    const itemType = item.dataset.type;
    const itemName = item.querySelector('.resource-name').textContent;
    
    // 🎯 如果是自定义模型或节点模型，先检查是否已加载
    if (itemType === 'custom-model' || itemType === 'node-model') {
        const modelId = item.dataset.modelId;
        const loadStatus = modelCacheManager.getLoadingStatus(modelId);
        
        // 🎯 如果模型正在加载中或未加载，禁止拖拽
        if (loadStatus === 'loading' || loadStatus !== 'loaded') {
            return;
        }
        
        // 📊 输出模型库中模型的详细信息（获取完整的模型数据）
        try {
            const currentProject = projectManager.getCurrentProject();
            if (currentProject) {
                const modelType = itemType === 'node-model' ? 'node' : 'model';
                const response = await fetch(`/api/projects/${currentProject.id}/models/${modelId}?type=${modelType}`);
                const result = await response.json();
                if (result.success && result.data) {
                    await logModelLibraryInfo(result.data, itemType === 'node-model' ? '节点库' : '模型库');
                }
            }
        } catch (error) {
            console.warn('⚠️ 无法输出模型信息:', error);
        }
        
        // 保存模型数据
        const modelType = itemType === 'node-model' ? 'node' : 'model'; // 🎯 确定模型类型
        currentDragModelData = {
            modelId: modelId,
            modelPath: item.dataset.modelPath,
            modelFormat: item.dataset.modelFormat,
            name: itemName,
            modelType: modelType // 🎯 添加模型类型信息
        };
        window.currentDragModelData = currentDragModelData;
    } else {
        currentDragModelData = null;
        window.currentDragModelData = null;
    }
    
    // 设置待拖拽项（只有通过上面的检查才会执行到这里）
    pendingDragItem = {
        element: item,
        type: itemType,
        name: itemName,
        color: parseInt(item.dataset.color),
        isLoaded: true // 能到这里说明模型已加载（或不是模型）
    };
}

function startActualDrag(e) {
    if (!pendingDragItem) return;
    
    const manager = window._sceneManager;
    const currentScene = manager.getCurrentScene();
    if (!currentScene) return;
    
    // 🎯 到这里时，模型一定已加载（在 onDragStart 中已处理）
    currentDragType = pendingDragItem.type;
    // 创建名称标签（跟随鼠标）
    dragNameLabel = document.createElement('div');
    dragNameLabel.className = 'drag-name-label';
    dragNameLabel.textContent = pendingDragItem.name;
    dragNameLabel.style.left = e.clientX + 'px';
    dragNameLabel.style.top = e.clientY + 'px';
    document.body.appendChild(dragNameLabel);
    
    // 在场景中创建半透明预览模型
    createScenePreviewMesh(currentScene, currentDragType, pendingDragItem.color, e.clientX, e.clientY);
    
    pendingDragItem.element.classList.add('dragging');
    isDragging = true;
}

function createScenePreviewMesh(currentScene, type, color, clientX, clientY) {
    const camera = currentScene.camera;
    if (!camera) return;
    
    // 如果是自定义3D模型或节点模型，加载真实模型作为预览
    if ((type === 'custom-model' || type === 'node-model') && currentDragModelData) {
        loadRealModelPreview(currentScene, clientX, clientY);
        return;
    }
    
    // 几何体功能已禁用，直接返回
    console.warn('⚠️ 几何体功能已禁用');
    return;
    
    // 以下几何体预览代码已禁用
    // // 创建几何体（用于基础几何形状）
    // let geometry;
    // switch (type) {
    //     case 'cube':
    //         geometry = new THREE.BoxGeometry(1, 1, 1);
    //         break;
    //     case 'sphere':
    //         geometry = new THREE.SphereGeometry(0.5, 32, 32);
    //         break;
    //     case 'cylinder':
    //         geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
    //         break;
    //     case 'cone':
    //         geometry = new THREE.ConeGeometry(0.5, 1, 32);
    //         break;
    //     case 'torus':
    //         geometry = new THREE.TorusGeometry(0.5, 0.2, 16, 100);
    //         break;
    //     case 'plane':
    //         geometry = new THREE.PlaneGeometry(2, 2);
    //         break;
    //     default:
    //         geometry = new THREE.BoxGeometry(1, 1, 1);
    // }
    // 
    // // 创建半透明材质
    // const material = new THREE.MeshStandardMaterial({
    //     color: color,
    //     roughness: 0.5,
    //     metalness: 0.5,
    //     transparent: true,
    //     opacity: 0.6
    // });
    // 
    // dragPreviewMesh = new THREE.Mesh(geometry, material);
    // dragPreviewMesh.castShadow = true;
    // 
    // // 使用射线投射计算初始位置
    // updatePreviewMeshPosition(currentScene, clientX, clientY);
    // 
    // // 添加到场景
    // currentScene.scene.add(dragPreviewMesh);
}

/**
 * 加载真实模型作为拖拽预览
 */
function loadRealModelPreview(currentScene, clientX, clientY) {
    if (!currentDragModelData) return;
    
    const modelPath = currentDragModelData.modelPath.toLowerCase();
    const fileExtension = modelPath.split('.').pop();
    const isGLTF = modelPath.endsWith('.gltf') || modelPath.endsWith('.glb');
    const isFBX = modelPath.endsWith('.fbx');
    
    // 选择加载器
    let loader;
    if (isGLTF) {
        loader = new GLTFLoader();
    } else if (isFBX) {
        loader = new FBXLoader();
    } else {
        return;
    }
    
    // 确保路径正确
    const normalizedPath = currentDragModelData.modelPath.startsWith('/') 
        ? currentDragModelData.modelPath 
        : '/' + currentDragModelData.modelPath;
    
    const modelUrl = `http://localhost:7283${normalizedPath}`;
    loader.load(
        modelUrl,
        (loadedObject) => {
            // GLTF返回 {scene: ...}，FBX直接返回对象
            const model = isGLTF ? loadedObject.scene : loadedObject;
            
            // ✅ 根据文件格式判断 importScale
            let importScale = 1.0;
            if (isFBX) {
                importScale = 0.01; // FBX 使用厘米单位，转为米
            }
            
            // ⚠️ 保存重要信息到 userData，供后续使用
            model.userData.importScale = importScale;
            model.userData.fileType = fileExtension;
            
            // 应用物理缩放
            model.scale.set(importScale, importScale, importScale);
            
            // ⚠️ 重要：强制更新矩阵，确保缩放生效
            model.updateMatrixWorld(true);
            
            // 启用阴影
            model.traverse((child) => {
                if (child.isMesh) {
                    child.castShadow = true;
                    child.receiveShadow = true;
                }
            });
            
            // 设置为预览模型
            dragPreviewMesh = model;
            isPreviewModelLoaded = true; // 标记预览模型已加载
            
            // 设置初始位置
            updatePreviewMeshPosition(currentScene, clientX, clientY);
            
            // 添加到场景
            currentScene.scene.add(dragPreviewMesh);
        },
        undefined,
        (error) => {
            console.warn('⚠️ 预览模型加载失败，使用占位符:', error.message);
            // 加载失败，使用简单占位符
            const geometry = new THREE.BoxGeometry(1, 1, 1);
            const material = new THREE.MeshStandardMaterial({
                color: 0x888888,
                roughness: 0.5,
                metalness: 0.5
            });
            dragPreviewMesh = new THREE.Mesh(geometry, material);
            isPreviewModelLoaded = false; // 占位符不算真实模型
            updatePreviewMeshPosition(currentScene, clientX, clientY);
            currentScene.scene.add(dragPreviewMesh);
        }
    );
}

function updatePreviewMeshPosition(currentScene, clientX, clientY) {
    if (!dragPreviewMesh) return;
    
    const camera = currentScene.camera;
    if (!camera) return;
    
    // 计算鼠标标准化设备坐标
    const mouse = new THREE.Vector2();
    mouse.x = (clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(clientY / window.innerHeight) * 2 + 1;
    
    // 创建射线投射器
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(mouse, camera);
    
    // 创建水平面（y = 0）
    const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
    const intersectPoint = new THREE.Vector3();
    
    // 计算射线与平面的交点
    raycaster.ray.intersectPlane(plane, intersectPoint);
    
    if (intersectPoint) {
        // 对于3D模型，计算包围盒来确定底部位置
        if (currentDragType === 'custom-model' || currentDragType === 'node-model') {
            // 计算模型包围盒（在世界坐标系中）
            const box = new THREE.Box3().setFromObject(dragPreviewMesh);
            const size = box.getSize(new THREE.Vector3());
            
            // 获取当前几何中心（在世界坐标系中）
            const currentCenter = box.getCenter(new THREE.Vector3());
            
            // 计算模型原点相对于几何中心的偏移量
            const offsetX = currentCenter.x - dragPreviewMesh.position.x;
            const offsetZ = currentCenter.z - dragPreviewMesh.position.z;
            
            // 先设置到目标位置
            dragPreviewMesh.position.copy(intersectPoint);
            dragPreviewMesh.position.y = size.y / 2; // 底部在地面
            
            // ⚠️ 关键：反向应用偏移，使几何中心对齐到目标位置（与最终模型保持一致）
            dragPreviewMesh.position.x -= offsetX;
            dragPreviewMesh.position.z -= offsetZ;
            
            // 调试日志（可选）
            // console.log(`📍 预览位置: (${dragPreviewMesh.position.x.toFixed(2)}, ${dragPreviewMesh.position.y.toFixed(2)}, ${dragPreviewMesh.position.z.toFixed(2)})`);
            // console.log(`   偏移量: X=${offsetX.toFixed(2)}, Z=${offsetZ.toFixed(2)}`);
        } else {
            // 基础几何体使用固定偏移
            let yOffset = 0.5;
            if (currentDragType === 'plane') {
                yOffset = 0;
            }
            dragPreviewMesh.position.set(intersectPoint.x, yOffset, intersectPoint.z);
        }
    }
}

function cleanupScenePreview(currentScene) {
    if (dragPreviewMesh && currentScene && currentScene.scene) {
        currentScene.scene.remove(dragPreviewMesh);
        
        // 递归释放资源（对于复杂模型）
        dragPreviewMesh.traverse((child) => {
            if (child.isMesh) {
                if (child.geometry) child.geometry.dispose();
                if (child.material) {
                    if (Array.isArray(child.material)) {
                        child.material.forEach(m => m.dispose());
                    } else {
                        child.material.dispose();
                    }
                }
            }
        });
        
        dragPreviewMesh = null;
    }
    isPreviewModelLoaded = false; // 重置预览加载标记
}

function onDragMove(e) {
    // 检查是否需要开始拖拽
    if (pendingDragItem && !isDragging) {
        const deltaX = Math.abs(e.clientX - dragStartX);
        const deltaY = Math.abs(e.clientY - dragStartY);
        
        // 只有移动超过阈值才开始真正的拖拽
        if (deltaX > dragThreshold || deltaY > dragThreshold) {
            startActualDrag(e);
        }
        return;
    }
    
    // ✅ 关键：如果拖拽已停止（isDragging = false），不再更新预览
    if (!currentDragType || !isDragging) return;
    
    // 更新名称标签位置
    if (dragNameLabel) {
        dragNameLabel.style.left = (e.clientX + 15) + 'px';
        dragNameLabel.style.top = (e.clientY + 15) + 'px';
    }
    
    // 更新场景中预览模型的位置
    const manager = window._sceneManager;
    const currentScene = manager.getCurrentScene();
    if (currentScene) {
        updatePreviewMeshPosition(currentScene, e.clientX, e.clientY);
    }
}

function onDragEnd(e) {
    // 如果没有真正开始拖拽，只是点击，直接清理状态
    if (pendingDragItem && !isDragging) {
        pendingDragItem = null;
        currentDragModelData = null;
        window.currentDragModelData = null;
        return;
    }
    
    if (!currentDragType || !isDragging) {
        pendingDragItem = null;
        window.currentDragModelData = null;
        return;
    }
    
    const manager = window._sceneManager;
    const currentScene = manager.getCurrentScene();
    
    // 移除名称标签
    if (dragNameLabel) {
        dragNameLabel.remove();
        dragNameLabel = null;
    }
    
    // ⚠️ 重要：不要在这里清理预览模型，让addResourceToScene或场景树处理
    // 预览模型可能会被复用，避免重复加载
    // cleanupScenePreview(currentScene); // 移除这行
    
    // 移除拖拽样式
    document.querySelectorAll('.resource-item.dragging').forEach(item => {
        item.classList.remove('dragging');
    });
    
    // 检查鼠标释放位置是否在场景树面板上
    const isOverSceneTree = checkIfOverSceneTree(e.clientX, e.clientY);
    
    if (isOverSceneTree) {
        // 延迟检查，给场景树节点的 drop 事件时间执行
        setTimeout(() => {
            // 如果 window.currentDragModelData 仍然存在，说明没有被场景树节点消费
            if (window.currentDragModelData) {
                console.warn('⚠️ 场景树未处理，这不应该发生');
                // 场景树未处理，清理预览模型
                cleanupScenePreview(currentScene);
            } else {
                // 场景树已处理，不清理（场景树会自己处理）
            }
            
            // 重置状态
            currentDragType = null;
            currentDragModelData = null;
            window.currentDragModelData = null;
            pendingDragItem = null;
            isDragging = false;
        }, 10);
    } else {
        // 拖拽到场景中，添加新节点
        addResourceToScene(currentDragType, e.clientX, e.clientY);
        
        // 重置状态
        currentDragType = null;
        currentDragModelData = null;
        window.currentDragModelData = null;
        pendingDragItem = null;
        isDragging = false;
    }
}

/**
 * 清理拖拽状态（供场景树调用）
 * 当场景树成功处理模型拖拽后，需要立即停止拖拽并清理预览
 */
window.cleanupModelDrag = function() {
    const manager = window._sceneManager;
    const currentScene = manager?.getCurrentScene();
    // 移除名称标签
    if (dragNameLabel) {
        dragNameLabel.remove();
        dragNameLabel = null;
    }
    
    // 清理场景预览模型
    cleanupScenePreview(currentScene);
    
    // 移除拖拽样式
    document.querySelectorAll('.resource-item.dragging').forEach(item => {
        item.classList.remove('dragging');
    });
    
    // 重置状态
    currentDragType = null;
    currentDragModelData = null;
    window.currentDragModelData = null;
    pendingDragItem = null;
    isDragging = false;
};

/**
 * 检查鼠标位置是否在场景树面板上
 * @param {number} clientX - 鼠标 X 坐标
 * @param {number} clientY - 鼠标 Y 坐标
 * @returns {boolean} 是否在场景树面板上
 */
function checkIfOverSceneTree(clientX, clientY) {
    // 查找场景树面板元素（左侧面板）
    const scenePanel = document.getElementById('scene-panel');
    if (!scenePanel) {
        console.warn('⚠️ 未找到场景树面板元素 #scene-panel');
        return false;
    }
    
    // 获取面板的边界矩形
    const rect = scenePanel.getBoundingClientRect();
    
    // 检查面板是否有宽度（可能被隐藏）
    if (rect.width === 0 || rect.height === 0) {
        return false;
    }
    
    // 检查鼠标是否在面板范围内
    const isOver = clientX >= rect.left && 
                   clientX <= rect.right && 
                   clientY >= rect.top && 
                   clientY <= rect.bottom;
    
    if (isOver) {
    }
    
    return isOver;
}

/**
 * 获取几何体的中文名称
 * 已禁用 - 不再需要几何体功能
 */
// function getGeometryChineseName(type) {
//     const nameMap = {
//         'cube': '立方体',
//         'sphere': '球体',
//         'cylinder': '圆柱',
//         'cone': '圆锥',
//         'torus': '圆环',
//         'plane': '平面'
//     };
//     return nameMap[type] || type;
// }

/**
 * 将3D对象添加到场景树
 * @param {THREE.Object3D} object3D - 要添加的3D对象
 * @param {string} name - 节点名称
 * @param {string} modelId - 模型ID（可选，如果使用了模型库中的模型）
 */
async function addToSceneTree(object3D, name, modelId = null, modelType = 'model') {
    
    // 🎯 如果是节点组模型，不在这里显示进度条（在 createHierarchyNodes 中处理）
    const shouldShowProgress = modelType !== 'node';
    if (shouldShowProgress) {
        // 显示进度条：单个模型加载
        loadingProgressManager.show();
        loadingProgressManager.setCategoryTotal('model', 1);
        loadingProgressManager.setCategoryTotal('material', 1);
        loadingProgressManager.updateCategoryItem('model', name);
    }
    
    // 获取场景树管理器和UI（从全局变量）
    const sceneTreeManager = window.sceneTreeManager;
    const sceneTreeUI = window.sceneTreeUI;
    
    if (!sceneTreeManager || !sceneTreeUI) {
        console.warn('⚠️ 场景树未初始化，无法添加节点');
        if (shouldShowProgress) {
            loadingProgressManager.hide();
        }
        return;
    }
    
    // 获取当前场景ID
    const manager = window._sceneManager;
    const currentSceneName = manager ? manager.getCurrentSceneName() : null;
    
    if (!currentSceneName) {
        console.warn('⚠️ 无法获取当前场景ID');
        return;
    }
    
    // 获取当前项目（后面应用材质时需要用到）
    const currentProject = window.projectManager?.getCurrentProject();
    
    // ✅ 获取当前选中的节点（稍后作为目标父节点）
    let selectedNode = sceneTreeManager.selectedNode;
    
    // 验证选中节点是否有效（是否真实存在于场景树中）
    if (selectedNode && !sceneTreeManager.findNodeById(selectedNode.id)) {
        console.warn(`⚠️ 选中的节点不存在于当前场景树，使用根节点替代: ${selectedNode.id}`);
        selectedNode = null;
        sceneTreeManager.deselectNode(); // 使用统一的取消选择接口
    }
    
    // 🎯 新逻辑：如果是节点组模型（node-model），根据层级关系创建多个节点
    if (modelType === 'node') {
        console.log('🌳 [addToSceneTree] 检测到节点组模型，根据层级关系创建节点');
        await createHierarchyNodes(object3D, name, modelId, modelType, sceneTreeManager, sceneTreeUI, selectedNode, currentSceneName, currentProject);
        return;
    }
    
    // ✅ 原有逻辑：普通模型创建单个节点
    
    // ✅ 策略改变：先添加到根节点（世界坐标），再移动到选中节点
    // 这样可以避免因父节点缩放过小导致子节点缩放值异常大的问题
    
    // 1. 保存目标世界坐标（来自拖拽位置）
    const targetWorldPosition = new THREE.Vector3(
        object3D.position.x,
        object3D.position.y,
        object3D.position.z
    );
    const targetWorldRotation = new THREE.Euler(0, 0, 0); // 世界旋转为(0,0,0)
    const targetWorldScale = new THREE.Vector3(1, 1, 1);   // 世界缩放为(1,1,1)（用户缩放，不包括 importScale）
    // 🔍 从模型库获取模型数据，查看是否有meshMaterials配置，并验证材质是否存在
    let meshMaterials = {};
    if (modelId) {
        try {
            const currentProject = window.projectManager?.getCurrentProject();
            if (currentProject) {
                // 🎯 添加 type 参数以支持节点模型
                const typeParam = modelType === 'node' ? '?type=node' : '';
                const response = await fetch(`/api/projects/${currentProject.id}/models/${modelId}${typeParam}`);
                if (response.ok) {
                    const result = await response.json();
                    if (result.success && result.data && result.data.meshMaterials) {
                        const modelLibraryMeshMaterials = result.data.meshMaterials;
                        
                        // ⚠️ 重要：验证并过滤材质绑定（只保留有效的材质）
                        // 使用全局函数获取材质库管理器
                        let materialLibraryManager = null;
                        if (window.getMaterialLibraryManager) {
                            materialLibraryManager = window.getMaterialLibraryManager(currentProject.id);
                        }
                        
                        if (materialLibraryManager) {
                            // 确保材质库已加载
                            if (!materialLibraryManager.materials || materialLibraryManager.materials.length === 0) {
                                await materialLibraryManager.loadMaterials();
                            }
                            
                            let copiedCount = 0;
                            let skippedCount = 0;
                            
                            // ✅ 新架构：从模型库复制meshMaterials（字符串格式）
                            // meshMaterials 格式：{ "meshName": "materialName" }
                            console.log(`📋 [addToSceneTree] 从模型库复制材质绑定:`, modelLibraryMeshMaterials);
                            
                            for (const [meshName, materialName] of Object.entries(modelLibraryMeshMaterials)) {
                                if (!materialName || typeof materialName !== 'string') {
                                    console.warn(`   ⚠️ Mesh "${meshName}" 没有有效的材质名，跳过`);
                                    skippedCount++;
                                    continue;
                                }
                                
                                // 验证材质在材质库中是否存在（按 displayName 查找）
                                const materialExists = materialLibraryManager.materials.some(
                                    mat => mat.displayName === materialName
                                );
                                
                                if (materialExists) {
                                    // ✅ 材质存在，直接保存材质名（字符串格式）
                                    meshMaterials[meshName] = materialName;
                                    copiedCount++;
                                    console.log(`   ✅ Mesh "${meshName}" → 材质 "${materialName}"`);
                                } else {
                                    // 材质不存在，跳过（不保存）
                                    skippedCount++;
                                    console.warn(`   ⚠️ 跳过材质绑定 (材质不存在): Mesh "${meshName}" → 材质 "${materialName}"`);
                                }
                            }
                            
                            console.log(`📊 [addToSceneTree] 材质复制完成: 成功 ${copiedCount} 个，跳过 ${skippedCount} 个`);
                            if (skippedCount > 0) {
                                console.warn(`⚠️ 跳过了 ${skippedCount} 个材质绑定（材质不存在）`);
                            }
                            
                        } else {
                            console.warn('⚠️ 材质库管理器未初始化，无法验证材质');
                        }
                    }
                }
            }
        } catch (error) {
            console.warn('⚠️ 获取模型库数据失败:', error);
        }
    }
    
    // 2. 创建场景树节点，先添加到根节点，使用世界坐标作为 transform
    const nodeData = {
        name: name,
        type: object3D.type || 'Object3D',
        object3D: object3D,
        visible: true,
        locked: false,
        // ✅ 自动添加模型组件（modelId 存储在组件中，不在节点上）
        components: modelId ? [
            {
                type: 'model',
                modelId: modelId,
                modelName: name,
                modelType: modelType, // 🎯 保存模型类型（'model' 或 'node'）
                meshMaterials: meshMaterials // 🎯 添加材质绑定数据
            }
        ] : [],
        // ✅ 使用世界坐标作为 transform（因为添加到根节点，世界坐标 = 本地坐标）
        transform: {
            position: {
                x: parseFloat(targetWorldPosition.x.toFixed(3)),
                y: parseFloat(targetWorldPosition.y.toFixed(3)),
                z: parseFloat(targetWorldPosition.z.toFixed(3))
            },
            rotation: {
                x: parseFloat(targetWorldRotation.x.toFixed(4)), // 弧度值
                y: parseFloat(targetWorldRotation.y.toFixed(4)),
                z: parseFloat(targetWorldRotation.z.toFixed(4))
            },
            scale: {
                x: parseFloat(targetWorldScale.x.toFixed(3)),
                y: parseFloat(targetWorldScale.y.toFixed(3)),
                z: parseFloat(targetWorldScale.z.toFixed(3))
            }
        }
    };
    
    const newNode = sceneTreeManager.addNode(nodeData, sceneTreeManager.root.id); // ✅ 先添加到根节点
    
    if (newNode) {
        // ✅ 完成模型加载
        if (shouldShowProgress) {
            loadingProgressManager.incrementCategory('model');
        }
        
        // 🎨 立即应用材质绑定到模型（如果有的话）
        if (modelId && Object.keys(meshMaterials).length > 0) {
            console.log('🎨 [addToSceneTree] 立即应用材质绑定到模型');
            if (shouldShowProgress) {
                loadingProgressManager.updateCategoryItem('material', '应用材质绑定');
            }
            const modelComponent = newNode.components?.find(c => c.type === 'model');
            if (modelComponent && currentProject) {
                try {
                    await sceneTreeManager.applyMeshMaterials(newNode, modelComponent, currentProject.id);
                    console.log('✅ [addToSceneTree] 材质应用完成');
                    if (shouldShowProgress) {
                        loadingProgressManager.incrementCategory('material');
                    }
                } catch (error) {
                    console.error('❌ [addToSceneTree] 材质应用失败:', error);
                    if (shouldShowProgress) {
                        loadingProgressManager.incrementCategory('material');
                    }
                }
            }
        } else {
            // 没有材质绑定，也计数
            if (shouldShowProgress) {
                loadingProgressManager.incrementCategory('material');
            }
        }
        
        // 3. 如果有选中节点，将新节点移动到选中节点下
        if (selectedNode && selectedNode.id !== sceneTreeManager.root.id) {
            // 使用 moveNode 方法（会自动使用 attach() 保持世界坐标不变）
            const moveSuccess = sceneTreeManager.moveNode(newNode.id, selectedNode.id);
            
            if (moveSuccess) {
            } else {
                console.warn(`⚠️ 移动节点到 "${selectedNode.name}" 失败，节点保留在根节点下`);
            }
        }
        
        // 刷新场景树UI
        sceneTreeUI.refresh();
        
        // 触发场景树变化事件（自动保存）
        sceneTreeUI.notifyTreeChanged();
        
        // 如果有模型ID，更新模型的引用节点列表
        if (modelId && currentSceneName) {
            updateModelNodeReferences(modelId, currentSceneName, newNode.id, 'add', modelType);
        }
    } else {
        console.error(`❌ 创建场景树节点失败: "${name}"`);
        if (shouldShowProgress) {
            loadingProgressManager.hide();
        }
    }
}

/**
 * 🌳 根据模型的层级结构创建对应的场景树节点
 * @param {THREE.Object3D} rootObject - 根模型对象
 * @param {string} name - 根节点名称
 * @param {string} modelId - 模型ID
 * @param {string} modelType - 模型类型
 * @param {object} sceneTreeManager - 场景树管理器
 * @param {object} sceneTreeUI - 场景树UI
 * @param {object} selectedNode - 当前选中的节点
 * @param {string} currentSceneName - 当前场景名称
 * @param {object} currentProject - 当前项目
 */
async function createHierarchyNodes(rootObject, name, modelId, modelType, sceneTreeManager, sceneTreeUI, selectedNode, currentSceneName, currentProject) {
    console.log(`🌳 [createHierarchyNodes] 开始创建层级节点，根对象: ${name}`);
    
    // 🎯 显示进度条并计算节点数量
    // 先遍历计算总节点数（排除Mesh数据节点）
    let totalNodeCount = 0;
    const countNodes = (object3D, isRoot = false) => {
        const objectName = object3D.name || '';
        const isMeshData = objectName.startsWith('Mesh');
        
        if (isMeshData && !isRoot) return; // 跳过Mesh数据节点及其子孙
        if (object3D.isCamera || object3D.isLight || object3D.isHelper) return;
        
        totalNodeCount++;
        object3D.children.forEach(child => {
            const childName = child.name || '';
            if (!childName.startsWith('Mesh')) {
                countNodes(child, false);
            }
        });
    };
    countNodes(rootObject, true);
    
    console.log(`📊 [createHierarchyNodes] 预计创建 ${totalNodeCount} 个节点`);
    
    // 显示进度条
    loadingProgressManager.show();
    loadingProgressManager.setCategoryTotal('hierarchy', totalNodeCount);
    loadingProgressManager.setCategoryTotal('material', totalNodeCount); // 每个节点可能有材质
    
    // 🎯 预加载所有材质数据到缓存
    const materialDataCache = new Map();
    try {
        const projectId = currentProject?.id;
        if (projectId) {
            console.log('📦 [createHierarchyNodes] 正在加载材质库数据...');
            const response = await fetch(`/api/projects/${projectId}/materials`);
            if (response.ok) {
                const result = await response.json();
                if (result.success && result.data) {
                    result.data.forEach(mat => {
                        if (mat.displayName) {
                            materialDataCache.set(mat.displayName, mat);
                        }
                        // 同时用 name 作为键（兼容性）
                        if (mat.name && mat.name !== mat.displayName) {
                            materialDataCache.set(mat.name, mat);
                        }
                    });
                    console.log(`✅ [createHierarchyNodes] 材质库已加载: ${materialDataCache.size} 个材质`);
                }
            }
        }
    } catch (error) {
        console.warn('⚠️ [createHierarchyNodes] 加载材质库失败:', error);
    }
    
    // 🎯 处理根对象是Mesh数据的情况：创建一个Group包装器
    // 判断条件：只判断名称是否以"Mesh"开头
    let actualRootObject = rootObject;
    let needsWrapper = false;
    const rootObjectName = rootObject.name || '';
    const isRootMeshData = rootObjectName.startsWith('Mesh');
    
    // ✅ 保存原始的缩放和 importScale（在创建 wrapper 之前）
    const originalRootScale = rootObject.scale.clone();
    const originalImportScale = rootObject.userData?.importScale || 1.0;
    
    if (isRootMeshData) {
        console.log(`⚠️ 根对象本身是Mesh数据 (名称以Mesh开头: ${rootObjectName})，创建Group包装器`);
        needsWrapper = true;
        const wrapper = new THREE.Group();
        wrapper.name = name;
        
        // ✅ 将 importScale 复制到 wrapper 的 userData
        if (!wrapper.userData) {
            wrapper.userData = {};
        }
        wrapper.userData.importScale = originalImportScale;
        
        // 保存原始位置
        const originalPosition = rootObject.position.clone();
        const originalRotation = rootObject.rotation.clone();
        
        // 将Mesh数据添加到wrapper
        wrapper.add(rootObject);
        
        // 重置Mesh数据为本地坐标原点（因为现在它是wrapper的子对象）
        rootObject.position.set(0, 0, 0);
        rootObject.rotation.set(0, 0, 0);
        // scale保持不变
        
        // ✅ wrapper 继承原始的缩放（包含 importScale）
        wrapper.position.copy(originalPosition);
        wrapper.rotation.copy(originalRotation);
        wrapper.scale.copy(originalRootScale); // 使用原始的 scale（包含 importScale）
        
        // 🎯 重要：将 Mesh 的 scale 重置为 (1,1,1)，因为缩放已经应用到 wrapper
        rootObject.scale.set(1, 1, 1);
        
        actualRootObject = wrapper;
    }
    
    // 保存根对象的世界坐标
    const rootWorldPosition = actualRootObject.position.clone();
    const rootWorldRotation = new THREE.Euler(0, 0, 0);
    // ✅ 修复：使用实际的缩放（包含 importScale）
    const rootWorldScale = actualRootObject.scale.clone();
    
    // 递归创建节点的辅助函数
    const createNodeRecursive = async (object3D, parentNodeId, isRoot = false) => {
        // 🎯 判断是否为Mesh数据：只判断名称是否以"Mesh"开头（大小写敏感）
        const objectName = object3D.name || '';
        const isMeshData = objectName.startsWith('Mesh');
        
        // 🎯 跳过Mesh数据节点（不创建场景树节点，作为根节点的mesh数据）
        // ⚠️ 重要：return null后不再递归处理Mesh的子节点
        // 以"Mesh"开头的节点及其所有子孙节点作为一个整体，作为根节点的mesh数据
        if (isMeshData) {
            console.log(`   ⏩ 跳过Mesh数据树: ${objectName} (名称以Mesh开头)，该节点及所有子孙节点作为根节点的mesh数据`);
            return null;
        }
        
        // 跳过相机、灯光等非必要对象
        if (object3D.isCamera || object3D.isLight || object3D.isHelper) {
            console.log(`   ⏩ 跳过非必要对象: ${objectName} (${object3D.type})`);
            return null;
        }
        
        // 🎯 判断子对象中是否有非Mesh数据的对象
        const hasNonMeshChildren = object3D.children.some(child => {
            const childName = child.name || '';
            const isChildMeshData = childName.startsWith('Mesh');
            return !isChildMeshData && !child.isCamera && !child.isLight && !child.isHelper;
        });
        
        const hasMeshChildren = object3D.children.some(child => {
            const childName = child.name || '';
            return childName.startsWith('Mesh');
        });
        
        // 🎯 修改逻辑：不要跳过只包含Mesh的Group
        // 因为这些Mesh需要在该节点的Mesh模式中显示
        // 只有当Group既没有Mesh也没有有意义的子对象时才跳过
        if (!isRoot && !hasNonMeshChildren && !hasMeshChildren) {
            console.log(`   ⏩ 跳过空Group: ${objectName || 'Unnamed'}`);
            return null;
        }
        
        // 获取对象的本地变换
        const localPosition = object3D.position.clone();
        const localRotation = object3D.rotation.clone();
        const localScale = object3D.scale.clone();
        
        // 收集当前对象的所有Mesh（用于材质绑定）
        let meshMaterials = {};
        
        // 🎯 收集所有 mesh 并尝试自动绑定同名材质
        object3D.traverse((child) => {
            if (child.isMesh && child.name) {
                // 获取 mesh 的材质名称
                const material = child.material;
                if (material && material.name) {
                    // 尝试在材质库中查找同名材质
                    const materialName = material.name;
                    
                    // 检查材质库中是否存在同名材质
                    if (materialDataCache.has(materialName)) {
                        meshMaterials[child.name] = materialName;
                        console.log(`   🎨 自动绑定材质: ${child.name} -> ${materialName}`);
                    } else {
                        // 如果没有找到同名材质，不绑定（使用模型自带材质）
                        meshMaterials[child.name] = "0";
                        console.log(`   ⚪ Mesh "${child.name}" 未找到同名材质 "${materialName}"，使用模型自带材质`);
                    }
                } else {
                    // 没有材质名称，不绑定
                    meshMaterials[child.name] = "0";
                }
            }
        });
        
        // 🎯 统计Mesh数据数量（只统计直接子节点中以"Mesh"开头的，不深入Mesh的子孙节点）
        let meshCount = 0;
        object3D.children.forEach((child) => {
            const childName = child.name || '';
            const isChildMeshData = childName.startsWith('Mesh');
            if (isChildMeshData) {
                meshCount++;
                console.log(`   📦 发现Mesh数据: ${childName} (名称以Mesh开头，该节点及子孙不生成TreeNode)`);
            }
        });
        
        console.log(`   📊 节点 "${objectName || 'Unnamed'}" 的直接子节点中有 ${meshCount} 个Mesh数据，共 ${object3D.children.length} 个直接子对象`);
        
        // 创建场景树节点数据
        const nodeName = object3D.name || (isRoot ? name : 'Object');
        const nodeData = {
            name: nodeName,
            type: object3D.type || 'Object3D',
            object3D: object3D,
            visible: object3D.visible !== false,
            locked: false,
            components: [],
            // 🎯 标记节点来自节点组模型
            userData: {
                fromHierarchy: true // 标记这是从节点组模型创建的节点
            },
            transform: {
                position: {
                    x: parseFloat(localPosition.x.toFixed(3)),
                    y: parseFloat(localPosition.y.toFixed(3)),
                    z: parseFloat(localPosition.z.toFixed(3))
                },
                rotation: {
                    x: parseFloat(localRotation.x.toFixed(4)),
                    y: parseFloat(localRotation.y.toFixed(4)),
                    z: parseFloat(localRotation.z.toFixed(4))
                },
                scale: {
                    x: parseFloat(localScale.x.toFixed(3)),
                    y: parseFloat(localScale.y.toFixed(3)),
                    z: parseFloat(localScale.z.toFixed(3))
                },
                // 🔑 标记为本地坐标（相对于父节点），而非世界坐标
                isLocalCoordinates: true
            }
        };
        
        // 🎯 如果节点有 mesh 数据，添加模型组件（标记为只读，不可删除和更换）
        if (meshCount > 0 || isRoot) {
            nodeData.components.push({
                type: 'model',
                // 🔑 只有根节点保存 modelId，子节点不保存（避免重新加载时覆盖原有mesh）
                modelId: isRoot ? modelId : null,
                modelName: name,
                modelType: modelType,
                meshMaterials: meshMaterials,
                readonly: true,  // 🔒 标记为只读，防止删除和更换模型
                fromHierarchy: !isRoot  // 🎯 标记子节点来自层级结构，不需要重新加载模型
            });
        }
        
        // 🎯 如果是根节点，使用世界坐标
        if (isRoot) {
            // ✅ 获取 importScale 并转换为用户缩放
            // rootWorldScale 是物理缩放（包含 importScale），需要转换为用户缩放
            const rootImportScale = actualRootObject.userData?.importScale || 1.0;
            const userScale = {
                x: rootWorldScale.x / rootImportScale,
                y: rootWorldScale.y / rootImportScale,
                z: rootWorldScale.z / rootImportScale
            };
            
            // 根节点使用世界坐标
            nodeData.transform = {
                position: {
                    x: parseFloat(rootWorldPosition.x.toFixed(3)),
                    y: parseFloat(rootWorldPosition.y.toFixed(3)),
                    z: parseFloat(rootWorldPosition.z.toFixed(3))
                },
                rotation: {
                    x: parseFloat(rootWorldRotation.x.toFixed(4)),
                    y: parseFloat(rootWorldRotation.y.toFixed(4)),
                    z: parseFloat(rootWorldRotation.z.toFixed(4))
                },
                scale: {
                    x: parseFloat(userScale.x.toFixed(3)),
                    y: parseFloat(userScale.y.toFixed(3)),
                    z: parseFloat(userScale.z.toFixed(3))
                },
                // 🔑 根节点：世界坐标，非本地坐标
                isLocalCoordinates: false
            };
            
        }
        
        // 创建场景树节点
        const newNode = sceneTreeManager.addNode(nodeData, parentNodeId);
        
        if (!newNode) {
            console.error(`❌ 创建节点失败: ${nodeName}`);
            return null;
        }
        
        console.log(`   ✅ 创建TreeNode: ${nodeName} (父节点: ${parentNodeId}, type: ${object3D.type})`);
        
        // 📝 更新节点创建进度
        loadingProgressManager.updateCategoryItem('hierarchy', nodeName);
        loadingProgressManager.incrementCategory('hierarchy');
        
        // 🎨 应用材质绑定（如果有材质映射）
        if (Object.keys(meshMaterials).length > 0) {
            const modelComponent = newNode.components?.find(c => c.type === 'model');
            if (modelComponent && currentProject?.id) {
                try {
                    console.log(`   🎨 开始应用材质绑定...`);
                    loadingProgressManager.updateCategoryItem('material', `${nodeName} 的材质`);
                    await sceneTreeManager.applyMeshMaterials(newNode, modelComponent, currentProject.id);
                    console.log(`   ✅ 材质应用完成`);
                    loadingProgressManager.incrementCategory('material');
                } catch (error) {
                    console.error(`   ❌ 材质应用失败:`, error);
                    loadingProgressManager.incrementCategory('material'); // 失败也算完成
                }
            }
        } else {
            // 没有材质绑定，也计数
            loadingProgressManager.incrementCategory('material');
        }
        
        // 🎯 如果是根节点，更新模型引用
        if (isRoot && modelId && currentSceneName) {
            await updateModelNodeReferences(modelId, currentSceneName, newNode.id, 'add', modelType);
        }
        
        // 递归创建子节点（只处理非Mesh数据的子对象，Mesh数据作为根节点的渲染内容）
        for (const child of object3D.children) {
            const childName = child.name || '';
            const isChildMeshData = childName.startsWith('Mesh');
            
            // 如果子对象是Mesh数据（名称以"Mesh"开头），跳过（作为根节点的mesh数据）
            if (isChildMeshData) {
                console.log(`   ⏩ 跳过Mesh数据子对象: ${childName}，该节点及子孙作为根节点的mesh数据`);
                continue;
            }
            
            // 递归处理非Mesh数据的子对象
            await createNodeRecursive(child, newNode.id, false);
        }
        
        return newNode;
    };
    
    // 从根对象开始创建
    const rootNodeParentId = selectedNode && selectedNode.id !== sceneTreeManager.root.id 
        ? selectedNode.id 
        : sceneTreeManager.root.id;
    
    console.log(`🎯 开始从根对象创建节点树...`);
    const rootNode = await createNodeRecursive(actualRootObject, rootNodeParentId, true);
    
    if (rootNode) {
        console.log(`✅ [createHierarchyNodes] 层级节点创建完成`);
        console.log(`📊 创建的TreeNode数量: ${sceneTreeManager.getNodeCount()}`);
        
        // 🎯 如果使用了wrapper，需要确保wrapper被添加到场景中
        if (needsWrapper) {
            const manager = window._sceneManager;
            const currentScene = manager.getCurrentScene();
            if (currentScene && currentScene.scene && !actualRootObject.parent) {
                currentScene.scene.add(actualRootObject);
                console.log(`✅ Wrapper已添加到场景中`);
            }
        }
        
        // 刷新场景树UI
        sceneTreeUI.refresh();
        
        // 触发场景树变化事件（自动保存）
        sceneTreeUI.notifyTreeChanged();
        
        // ✅ 进度条会自动检测完成并关闭
    } else {
        console.error(`❌ [createHierarchyNodes] 创建根节点失败`);
    }
}

/**
 * 更新模型的引用节点列表
 * @param {string} modelId - 模型ID
 * @param {string} sceneId - 场景ID
 * @param {string} nodeId - 节点ID
 * @param {string} action - 'add' 或 'remove'
 * @param {string} modelType - 模型类型（'model' 或 'node'）
 */
async function updateModelNodeReferences(modelId, sceneId, nodeId, action = 'add', modelType = 'model') {
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            console.warn('⚠️ 未找到当前项目');
            return;
        }
        
        // 🎯 添加 type 参数以支持节点模型
        const typeParam = modelType === 'node' ? '?type=node' : '';
        const url = `/api/projects/${currentProject.id}/models/${modelId}/node-references${typeParam}`;
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                sceneId: sceneId,
                nodeId: nodeId,
                action: action // 'add' 或 'remove'
            })
        });
        
        const result = await response.json();
        if (result.success) {
        } else {
            console.warn('⚠️ 更新模型引用失败:', result.error);
        }
    } catch (error) {
        console.warn('⚠️ 更新模型引用时出错:', error.message);
    }
}

/**
 * 添加资源（几何体或3D模型）到场景
 * 已移除几何体功能，仅保留3D模型
 */
function addResourceToScene(type, clientX, clientY) {
    const manager = window._sceneManager;
    const currentScene = manager.getCurrentScene();
    if (!currentScene || !currentScene.scene) {
        console.warn('⚠️ 当前没有激活的场景');
        return;
    }
    
    const camera = currentScene.camera;
    if (!camera) {
        console.warn('⚠️ 当前场景没有相机');
        return;
    }
    
    // 如果是自定义3D模型或节点模型，使用专门的加载函数
    if (type === 'custom-model' || type === 'node-model') {
        addCustomModelToScene(clientX, clientY);
        return;
    }
    
    console.warn('⚠️ 几何体功能已禁用，仅支持3D模型');
    
    // 以下几何体创建代码已禁用
    // console.log(`✨ 添加${type}到场景`);
    // 
    // // 创建几何体和材质
    // let geometry, material, mesh;
    // const geometryList = document.getElementById('geometry-list');
    // const item = geometryList.querySelector(`[data-type="${type}"]`);
    // const color = parseInt(item.dataset.color);
    // 
    // material = new THREE.MeshStandardMaterial({
    //     color: color,
    //     roughness: 0.5,
    //     metalness: 0.5
    // });
    // 
    // switch (type) {
    //     case 'cube':
    //         geometry = new THREE.BoxGeometry(1, 1, 1);
    //         break;
    //     case 'sphere':
    //         geometry = new THREE.SphereGeometry(0.5, 32, 32);
    //         break;
    //     case 'cylinder':
    //         geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
    //         break;
    //     case 'cone':
    //         geometry = new THREE.ConeGeometry(0.5, 1, 32);
    //         break;
    //     case 'torus':
    //         geometry = new THREE.TorusGeometry(0.5, 0.2, 16, 100);
    //         break;
    //     case 'plane':
    //         geometry = new THREE.PlaneGeometry(2, 2);
    //         break;
    //     default:
    //         console.warn('未知的几何体类型:', type);
    //         return;
    // }
    // 
    // mesh = new THREE.Mesh(geometry, material);
    // mesh.castShadow = true;
    // mesh.receiveShadow = true;
    // 
    // // 使用射线投射计算鼠标位置对应的3D坐标
    // const mouse = new THREE.Vector2();
    // mouse.x = (clientX / window.innerWidth) * 2 - 1;
    // mouse.y = -(clientY / window.innerHeight) * 2 + 1;
    // 
    // const raycaster = new THREE.Raycaster();
    // raycaster.setFromCamera(mouse, camera);
    // 
    // // 创建一个水平面用于投射（y = 0 的平面）
    // const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
    // const intersectPoint = new THREE.Vector3();
    // 
    // raycaster.ray.intersectPlane(plane, intersectPoint);
    // 
    // if (intersectPoint) {
    //     // 根据几何体类型调整Y坐标
    //     let yOffset = 0.5; // 默认偏移
    //     switch (type) {
    //         case 'cube':
    //             yOffset = 0.5;
    //             break;
    //         case 'sphere':
    //             yOffset = 0.5;
    //             break;
    //         case 'cylinder':
    //         case 'cone':
    //             yOffset = 0.5;
    //             break;
    //         case 'torus':
    //             yOffset = 0.5;
    //             break;
    //         case 'plane':
    //             yOffset = 0;
    //             break;
    //     }
    //     
    //     mesh.position.set(intersectPoint.x, yOffset, intersectPoint.z);
    //     console.log(`📍 模型位置: (${intersectPoint.x.toFixed(2)}, ${yOffset}, ${intersectPoint.z.toFixed(2)})`);
    // } else {
    //     // 如果射线投射失败，使用默认位置
    //     mesh.position.set(0, 0.5, 0);
    //     console.warn('⚠️ 无法计算放置位置，使用默认位置');
    // }
    // 
    // currentScene.scene.add(mesh);
    // 
    // console.log(`✅ ${type} 已添加到场景`);
    // 
    // // 自动创建场景树节点（几何体没有modelId）
    // addToSceneTree(mesh, getGeometryChineseName(type), null);
}

/**
 * 添加自定义3D模型到场景
 */
function addCustomModelToScene(clientX, clientY) {
    
    if (!currentDragModelData) {
        console.warn('⚠️ 没有模型数据');
        return;
    }
    
    const manager = window._sceneManager;
    const currentScene = manager.getCurrentScene();
    const camera = currentScene.camera;
    
    // ⚠️ 重要：保存模型数据的副本，因为加载是异步的，拖拽结束时会清空 currentDragModelData
    const modelData = { 
        ...currentDragModelData
    };
    
    // ✅ 优化：如果预览模型已加载，直接复用它，避免重复加载
    if (isPreviewModelLoaded && dragPreviewMesh) {
        console.log('✨ 复用预览模型，避免重复加载');
        
        // 直接使用预览模型
        const model = dragPreviewMesh;
        
        // 模型已经在场景中，已经有正确的位置和缩放
        // 直接创建场景树节点即可
        console.log('🎯 调用addToSceneTree创建节点');
        console.log('   模型名:', modelData.name);
        console.log('   模型ID:', modelData.modelId);
        
        // ⚠️ 重要：清空预览引用但不清理资源（模型已被接管）
        dragPreviewMesh = null;
        isPreviewModelLoaded = false;
        
        // 🎯 获取模型类型（从 currentDragType）
        const modelType = currentDragType === 'node-model' ? 'node' : 'model';
        
        // 注意：不要从场景中移除，addToSceneTree 会处理
        addToSceneTree(model, modelData.name, modelData.modelId, modelType);
        
        return;
    }
    
    // 如果没有预览模型，按原来的方式加载（兜底逻辑）
    console.log('⚠️ 预览模型未加载，重新加载模型');
    
    // 清理可能存在的预览模型（虽然标记说没加载，但可能有占位符）
    if (dragPreviewMesh) {
        cleanupScenePreview(currentScene);
    }
    
    // 检查模型格式
    const modelPath = modelData.modelPath.toLowerCase();
    const fileExtension = modelPath.split('.').pop();
    const isGLTF = modelPath.endsWith('.gltf') || modelPath.endsWith('.glb');
    const isFBX = modelPath.endsWith('.fbx');
    
    // 检查是否支持该格式
    if (!isGLTF && !isFBX) {
        console.error('❌ 不支持的模型格式');
        alert(`当前支持的格式: GLTF/GLB, FBX\n\n您的模型格式: ${fileExtension.toUpperCase()}\n\n请上传支持的格式。`);
        return;
    }
    // 计算放置位置
    const mouse = new THREE.Vector2();
    mouse.x = (clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(clientY / window.innerHeight) * 2 + 1;
    
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(mouse, camera);
    
    // 创建一个水平面用于投射（y = 0 的平面）
    const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
    const intersectPoint = new THREE.Vector3();
    raycaster.ray.intersectPlane(plane, intersectPoint);
    
    const targetPosition = intersectPoint || new THREE.Vector3(0, 0, 0);
    
    // 确保路径以斜杠开头
    const normalizedPath = modelData.modelPath.startsWith('/') 
        ? modelData.modelPath 
        : '/' + modelData.modelPath;
    
    const modelUrl = `http://localhost:7283${normalizedPath}`;
    // 根据格式选择合适的加载器
    let loader;
    if (isGLTF) {
        loader = new GLTFLoader();
    } else if (isFBX) {
        loader = new FBXLoader();
    }
    
    // 统一的模型处理函数
    const onModelLoaded = (loadedObject) => {
        
        // GLTF格式返回的是 { scene: ... }，FBX直接返回对象
        const model = isGLTF ? loadedObject.scene : loadedObject;
        
        const meshList = [];
        model.traverse((obj) => {
            if (obj.isMesh) {
                meshList.push({
                    meshName: obj.name,
                    meshUuid: obj.uuid,
                    materialType: obj.material?.type,
                    materialName: obj.material?.name,
                    materialId: obj.material?.userData?.materialId || null,
                    hasUserData: !!obj.material?.userData
                });
            }
        });
        console.table(meshList);
        
        // 先计算原始尺寸
        const originalBox = new THREE.Box3().setFromObject(model);
        const originalSize = originalBox.getSize(new THREE.Vector3());
        // ✅ 设置导入缩放系数（Import Scale）
        // FBX 通常使用厘米单位，需要转换为米（×0.01）
        // glTF/GLB 已是米单位，保持1.0
        let importScale = 1.0;
        if (isFBX) {
            importScale = 0.01;
        }
        
        // ⚠️ 重要：保存 importScale 到 userData
        model.userData.importScale = importScale;
        model.userData.fileType = fileExtension;
        // 应用物理缩放（importScale × userScale，初始 userScale=1）
        model.scale.set(importScale, importScale, importScale);
        // ⚠️ 重要：强制更新矩阵，确保缩放生效后再计算包围盒
        model.updateMatrixWorld(true);
        
        // 重新计算缩放后的包围盒
        const box = new THREE.Box3().setFromObject(model);
        const scaledSize = box.getSize(new THREE.Vector3());
        // 获取模型当前的几何中心（在世界坐标系中）
        const currentCenter = box.getCenter(new THREE.Vector3());
        
        // 计算模型原点相对于几何中心的偏移量
        const offsetX = currentCenter.x - model.position.x;
        const offsetZ = currentCenter.z - model.position.z;
        
        // 先设置到目标位置
        model.position.copy(targetPosition);
        model.position.y = scaledSize.y / 2; // 让模型底部刚好在地面（y=0）
        
        // ⚠️ 关键：反向应用偏移，使几何中心对齐到目标位置
        // 如果模型的几何中心不在模型原点，需要补偿这个偏移
        model.position.x -= offsetX;
        model.position.z -= offsetZ;
        // 启用阴影
        model.traverse((child) => {
            if (child.isMesh) {
                child.castShadow = true;
                child.receiveShadow = true;
            }
        });
        
        // ✅ 不要在这里添加到场景，让 addToSceneTree 来处理
        // ❌ 之前的错误：currentScene.scene.add(model);
        // 这会导致 addChild 时判断为"移动节点"，引发缩放值计算错误
        // 🎯 获取模型类型（从 currentDragType）
        const modelType = currentDragType === 'node-model' ? 'node' : 'model';
        
        // 自动创建场景树节点，传递模型ID（这里会自动添加到Three.js场景）
        console.log('🎯 [onModelLoaded] 调用addToSceneTree创建节点');
        console.log('   模型名:', modelData.name);
        console.log('   模型ID:', modelData.modelId);
        console.log('   模型类型:', modelType);
        addToSceneTree(model, modelData.name, modelData.modelId, modelType);
    };
    
    // 加载进度回调
    const onProgress = (progress) => {
        if (progress.total > 0) {
            const percentComplete = (progress.loaded / progress.total) * 100;
        }
    };
    
    // 错误处理回调
    const onError = (error) => {
        console.error('❌ 模型加载失败:', error);
        console.error('   模型路径:', modelUrl);
        console.error('   模型格式:', fileExtension.toUpperCase());
        alert(`模型加载失败: ${error.message || '未知错误'}\n\n格式: ${fileExtension.toUpperCase()}\n路径: ${normalizedPath}\n\n请确保模型文件存在且格式正确。`);
    };
    
    // 开始加载
    loader.load(modelUrl, onModelLoaded, onProgress, onError);
}

// ============ 模型库管理弹窗 ============

/**
 * 打开模型库管理弹窗
 */
window.openModelLibraryManage = function() {
    const modal = document.getElementById('model-library-manage-modal');
    modal.classList.add('active');
    
    // 初始化Tab切换事件（只初始化一次）
    if (!modal.dataset.tabsInitialized) {
        const tabButtons = modal.querySelectorAll('.model-manage-tabs .tab-btn');
        tabButtons.forEach(btn => {
            btn.addEventListener('click', function() {
                const tabName = this.dataset.tab;
                switchManageTab(tabName);
            });
        });
        modal.dataset.tabsInitialized = 'true';
    }
    
    loadModelLibraryManage();
};

/**
 * 关闭模型库管理弹窗
 */
window.closeModelLibraryManage = function() {
    const modal = document.getElementById('model-library-manage-modal');
    modal.classList.remove('active');
};

/**
 * 切换管理弹窗的Tab
 */
function switchManageTab(tabName) {
    const modal = document.getElementById('model-library-manage-modal');
    
    // 更新Tab按钮状态
    modal.querySelectorAll('.model-manage-tabs .tab-btn').forEach(btn => {
        if (btn.dataset.tab === tabName) {
            btn.classList.add('active');
        } else {
            btn.classList.remove('active');
        }
    });
    
    // 更新Tab内容显示
    const modelTab = document.getElementById('model-manage-tab');
    const nodeTab = document.getElementById('node-manage-tab');
    
    if (tabName === 'model-manage') {
        modelTab.classList.add('active');
        nodeTab.classList.remove('active');
    } else if (tabName === 'node-manage') {
        modelTab.classList.remove('active');
        nodeTab.classList.add('active');
        // 每次打开节点库都刷新列表（确保数据最新）
        if (window.loadNodeManageList && typeof window.loadNodeManageList === 'function') {
            window.loadNodeManageList();
        }
    }
}

/**
 * 加载模型库管理内容
 */
async function loadModelLibraryManage() {
    // 加载3D模型列表
    await window.loadModelManageList();
}

/**
 * 加载3D模型管理列表（从项目模型库读取）
 */
window.loadModelManageList = async function() {
    const container = document.getElementById('model-manage-list');
    container.innerHTML = '<div class="loading">加载中...</div>';
    
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            container.innerHTML = '<div class="loading">未选择项目</div>';
            return;
        }
        
        // 从项目API获取模型列表
        const response = await fetch(`/api/projects/${currentProject.id}/models`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error(result.error || '获取模型列表失败');
        }
        
        const models = result.data || [];
        
        // 更新模型总数
        const countEl = document.getElementById('manage-model-count');
        if (countEl) {
            countEl.textContent = `共 ${models.length} 个模型`;
        }
        
        if (models.length === 0) {
            container.innerHTML = '<div class="loading">暂无3D模型，点击上传按钮添加</div>';
            return;
        }
        
        container.innerHTML = models.map((model, index) => {
            const formatIcon = getModelFormatIcon(model.format);
            const sizeStr = formatFileSize(model.size);
            const scaleValue = model.scale || 0.01;
            
            // 显示信息：如果有自定义名称使用自定义名称，否则使用原始文件名
            const displayName = model.name;
            const originalFileName = model.originalName || model.filename;
            
            // 后台数据已保证正确，无需修复（只在上传前处理即可）
            const safeDisplayName = displayName || '';
            const safeOriginalFileName = originalFileName || '';
            
            // 计算依赖数
            const usedByNodes = model.usedByNodes || [];
            const dependencyCount = usedByNodes.length;
            
            return `
                <div class="manage-item" data-model-id="${model.id}" style="position: relative; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 10px 12px; margin-bottom: 8px;">
                    <!-- 角标 -->
                    <div class="manage-item-number" style="position: absolute; top: 6px; left: 8px; color: rgba(255,255,255,0.25); font-size: 11px; font-weight: 500;">${index + 1}</div>
                    
                    <!-- 主内容区 -->
                    <div style="padding-left: 26px;">
                        <!-- 模型名 -->
                        <div class="manage-item-name" title="${safeDisplayName}" style="color: #fff; font-size: 14px; font-weight: 600; margin-bottom: 6px;">${safeDisplayName}</div>
                        
                        <!-- 紧凑的信息行 -->
                        <div style="display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; font-size: 11px; color: rgba(255,255,255,0.6);">
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">${model.format.toUpperCase()}</span>
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">${sizeStr}</span>
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">缩放 ${(scaleValue * 100).toFixed(0)}%</span>
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">引用 <span style="color: #667eea; font-weight: 600;">${dependencyCount}</span></span>
                        </div>
                        
                        <!-- 统计信息 -->
                        <div class="manage-item-stats" id="stats-${model.id}" style="margin: 4px 0;">
                            <div style="color: rgba(255,255,255,0.4); font-size: 10px;">加载中...</div>
                        </div>
                        
                        ${safeOriginalFileName !== safeDisplayName ? `<div style="color: rgba(255,255,255,0.35); font-size: 10px; margin-top: 3px;">文件: ${safeOriginalFileName}</div>` : ''}
                        
                        <!-- 标签显示 -->
                        ${model.tags && model.tags.length > 0 ? `
                            <div class="manage-item-tags" style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px;">
                                ${model.tags.map(tag => `<span style="display: inline-flex; align-items: center; padding: 2px 6px; background: rgba(102, 126, 234, 0.3); border: 1px solid rgba(102, 126, 234, 0.5); border-radius: 10px; font-size: 10px; color: rgba(255, 255, 255, 0.9); white-space: nowrap;">🏷️ ${tag}</span>`).join('')}
                            </div>
                        ` : ''}
                        
                        <!-- 按钮区 -->
                        <div class="manage-item-actions" style="display: flex; gap: 6px; margin-top: 6px;">
                            <button onclick="viewModelStructure('${model.id}')" style="flex: 1; padding: 4px 8px; background: rgba(102, 126, 234, 0.2); border: 1px solid rgba(102, 126, 234, 0.3); border-radius: 4px; color: #fff; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(102, 126, 234, 0.3)'" onmouseleave="this.style.background='rgba(102, 126, 234, 0.2)'">结构</button>
                            <button onclick="viewModelDependencies('${model.id}')" style="flex: 1; padding: 4px 8px; background: rgba(253, 203, 110, 0.2); border: 1px solid rgba(253, 203, 110, 0.3); border-radius: 4px; color: #fdcb6e; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(253, 203, 110, 0.3)'" onmouseleave="this.style.background='rgba(253, 203, 110, 0.2)'">依赖</button>
                            <button onclick="editModelInfo('${model.id}')" style="flex: 1; padding: 4px 8px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 4px; color: #fff; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(255, 255, 255, 0.1)'" onmouseleave="this.style.background='rgba(255, 255, 255, 0.05)'">编辑</button>
                            <button onclick="deleteProjectModel('${model.id}')" style="flex: 1; padding: 4px 8px; background: rgba(244, 67, 54, 0.15); border: 1px solid rgba(244, 67, 54, 0.3); border-radius: 4px; color: #f44336; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(244, 67, 54, 0.25)'" onmouseleave="this.style.background='rgba(244, 67, 54, 0.15)'">删除</button>
                        </div>
                    </div>
                </div>
            `;
        }).join('');
        
        // ✅ 异步加载每个模型的统计信息
        const currentProjectId = currentProject.id;
        models.forEach(async (model, index) => {
            try {
                // 延迟加载，避免同时发起太多请求
                setTimeout(async () => {
                    await loadModelStats(model.id, currentProjectId);
                }, index * 200);
            } catch (error) {
                console.warn(`⚠️ 加载模型统计失败 (${model.id}):`, error);
                updateModelStatsDisplay(model.id, null, error.message);
            }
        });
        
    } catch (error) {
        console.error('❌ 加载模型列表失败:', error);
        container.innerHTML = '<div class="loading">加载失败: ' + error.message + '</div>';
    }
}

/**
 * 加载单个模型的统计信息
 * @param {string} modelId - 模型ID
 * @param {string} projectId - 项目ID
 * @param {string} modelType - 模型类型 ('model' 或 'node')
 */
async function loadModelStats(modelId, projectId, modelType = 'model') {
    try {
        // 分析模型结构
        const stats = await modelAnalyzer.analyzeModel(modelId, projectId, modelType);
        const displayInfo = modelAnalyzer.formatStatsForDisplay(stats);
        
        // 更新显示
        updateModelStatsDisplay(modelId, displayInfo);
        
    } catch (error) {
        console.error(`❌ [ModelStats] 分析模型失败 (${modelId}):`, error);
        updateModelStatsDisplay(modelId, null, error.message);
    }
}

/**
 * 更新模型统计信息显示
 * @param {string} modelId - 模型ID
 * @param {Object} displayInfo - 显示信息
 * @param {string} errorMessage - 错误信息（可选）
 */
function updateModelStatsDisplay(modelId, displayInfo, errorMessage = null) {
    const statsEl = document.getElementById(`stats-${modelId}`);
    if (!statsEl) return;

    if (errorMessage) {
        statsEl.innerHTML = `
            <div class="stats-error">
                <span class="stats-error-icon">⚠️</span>
                <span class="stats-error-text">分析失败: ${errorMessage}</span>
            </div>
        `;
        return;
    }

    if (!displayInfo || displayInfo.error) {
        statsEl.innerHTML = `
            <div class="stats-error">
                <span class="stats-error-icon">❌</span>
                <span class="stats-error-text">${displayInfo?.details || '分析失败'}</span>
            </div>
        `;
        return;
    }

    // 直接显示统计信息，紧凑排列
    statsEl.innerHTML = `
        <div style="display: flex; flex-wrap: wrap; gap: 4px; font-size: 11px;">
            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px; color: rgba(255,255,255,0.6);">节点 <span style="color: #667eea; font-weight: 600;">${displayInfo.details.nodes}</span></span>
            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px; color: rgba(255,255,255,0.6);">网格 <span style="color: #667eea; font-weight: 600;">${displayInfo.details.meshes}</span></span>
            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px; color: rgba(255,255,255,0.6);">顶点 <span style="color: #667eea; font-weight: 600;">${displayInfo.details.vertices}</span></span>
            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px; color: rgba(255,255,255,0.6);">材质 <span style="color: #667eea; font-weight: 600;">${displayInfo.details.materials}</span></span>
        </div>
    `;
}

// toggleModelStats 函数已移除，因为不再需要展开/收起功能

/**
 * 加载节点库管理列表（从项目节点库读取）
 */
window.loadNodeManageList = async function() {
    const container = document.getElementById('node-manage-list');
    container.innerHTML = '<div class="loading">加载中...</div>';
    
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            container.innerHTML = '<div class="loading">未选择项目</div>';
            return;
        }
        
        // 从项目API获取节点模型列表（type=node）
        const response = await fetch(`/api/projects/${currentProject.id}/models?type=node`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error(result.error || '获取节点模型列表失败');
        }
        
        const nodes = result.data || [];
        
        if (nodes.length === 0) {
            container.innerHTML = '<div class="loading">暂无节点模型，点击"上传节点模型组"按钮添加</div>';
            return;
        }
        
        // 渲染节点列表（使用与模型列表相同的格式）
        container.innerHTML = nodes.map((node, index) => {
            const formatIcon = getModelFormatIcon(node.format);
            const sizeStr = formatFileSize(node.size);
            const scaleValue = node.scale || 0.01;
            
            const displayName = node.name;
            const originalFileName = node.originalName || node.filename;
            const safeDisplayName = displayName || '';
            const safeOriginalFileName = originalFileName || '';
            
            const usedByNodes = node.usedByNodes || [];
            const dependencyCount = usedByNodes.length;
            
            return `
                <div class="manage-item" data-model-id="${node.id}" data-model-type="node" style="position: relative; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; padding: 10px 12px; margin-bottom: 8px;">
                    <div class="manage-item-number" style="position: absolute; top: 6px; left: 8px; color: rgba(255,255,255,0.25); font-size: 11px; font-weight: 500;">${index + 1}</div>
                    
                    <div style="padding-left: 26px;">
                        <div class="manage-item-name" title="${safeDisplayName}" style="color: #fff; font-size: 14px; font-weight: 600; margin-bottom: 6px;">${safeDisplayName}</div>
                        
                        <div style="display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; font-size: 11px; color: rgba(255,255,255,0.6);">
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">${node.format.toUpperCase()}</span>
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">${sizeStr}</span>
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">缩放 ${(scaleValue * 100).toFixed(0)}%</span>
                            <span style="background: rgba(0,0,0,0.2); padding: 2px 6px; border-radius: 3px;">引用 <span style="color: #667eea; font-weight: 600;">${dependencyCount}</span></span>
                        </div>
                        
                        <div class="manage-item-stats" id="stats-${node.id}" style="margin: 4px 0;">
                            <div style="color: rgba(255,255,255,0.4); font-size: 10px;">加载中...</div>
                        </div>
                        
                        ${safeOriginalFileName !== safeDisplayName ? `<div style="color: rgba(255,255,255,0.35); font-size: 10px; margin-top: 3px;">文件: ${safeOriginalFileName}</div>` : ''}
                        
                        ${node.tags && node.tags.length > 0 ? `
                            <div class="manage-item-tags" style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px;">
                                ${node.tags.map(tag => `<span style="display: inline-flex; align-items: center; padding: 2px 6px; background: rgba(102, 126, 234, 0.3); border: 1px solid rgba(102, 126, 234, 0.5); border-radius: 10px; font-size: 10px; color: rgba(255, 255, 255, 0.9); white-space: nowrap;">🏷️ ${tag}</span>`).join('')}
                            </div>
                        ` : ''}
                        
                        <div class="manage-item-actions" style="display: flex; gap: 6px; margin-top: 6px;">
                            <button onclick="viewModelStructure('${node.id}')" style="flex: 1; padding: 4px 8px; background: rgba(102, 126, 234, 0.2); border: 1px solid rgba(102, 126, 234, 0.3); border-radius: 4px; color: #fff; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(102, 126, 234, 0.3)'" onmouseleave="this.style.background='rgba(102, 126, 234, 0.2)'">结构</button>
                            <button onclick="viewModelDependencies('${node.id}')" style="flex: 1; padding: 4px 8px; background: rgba(253, 203, 110, 0.2); border: 1px solid rgba(253, 203, 110, 0.3); border-radius: 4px; color: #fdcb6e; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(253, 203, 110, 0.3)'" onmouseleave="this.style.background='rgba(253, 203, 110, 0.2)'">依赖</button>
                            <button onclick="editModelInfo('${node.id}')" style="flex: 1; padding: 4px 8px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 4px; color: #fff; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(255, 255, 255, 0.1)'" onmouseleave="this.style.background='rgba(255, 255, 255, 0.05)'">编辑</button>
                            <button onclick="deleteProjectModel('${node.id}')" style="flex: 1; padding: 4px 8px; background: rgba(244, 67, 54, 0.15); border: 1px solid rgba(244, 67, 54, 0.3); border-radius: 4px; color: #f44336; font-size: 11px; cursor: pointer; transition: all 0.15s;" onmouseenter="this.style.background='rgba(244, 67, 54, 0.25)'" onmouseleave="this.style.background='rgba(244, 67, 54, 0.15)'">删除</button>
                        </div>
                    </div>
                </div>
            `;
        }).join('');
        
        // 异步加载每个节点的统计信息
        const currentProjectId = currentProject.id;
        nodes.forEach(async (node, index) => {
            try {
                setTimeout(async () => {
                    await loadModelStats(node.id, currentProjectId, 'node');
                }, index * 200);
            } catch (error) {
                console.warn(`⚠️ 加载节点统计失败 (${node.id}):`, error);
                updateModelStatsDisplay(node.id, null, error.message);
            }
        });
        
    } catch (error) {
        console.error('❌ 加载节点模型列表失败:', error);
        container.innerHTML = '<div class="loading">加载失败: ' + error.message + '</div>';
    }
}

/**
 * 查看模型层级结构
 */
window.viewModelStructure = async function(modelId, modelType) {
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            window.showManageMessage('未选择项目', 'error');
            return;
        }
        
        // 如果没有传递modelType，尝试从DOM元素获取
        if (!modelType) {
            const modelItem = document.querySelector(`[data-model-id="${modelId}"]`);
            modelType = modelItem?.dataset.modelType || 'model';
        }
        
        // 构建API URL，添加type参数（如果是node类型）
        const typeParam = modelType === 'node' ? '?type=node' : '';
        
        // 获取模型信息
        const response = await fetch(`/api/projects/${currentProject.id}/models/${modelId}${typeParam}`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error('获取模型信息失败');
        }
        
        const model = result.data;
        const modelPath = `/api/projects/${currentProject.id}/models/${modelId}/file${typeParam}`;
        
        // 显示加载提示
        // window.showManageMessage('正在加载模型结构...', 'info'); // 已移除：不再显示加载提示
        
        // 根据文件格式选择加载器
        const extension = model.filename.toLowerCase().split('.').pop();
        let object3D;
        
        if (extension === 'fbx') {
            const loader = new FBXLoader();
            object3D = await new Promise((resolve, reject) => {
                loader.load(modelPath, resolve, undefined, reject);
            });
        } else if (extension === 'gltf' || extension === 'glb') {
            const loader = new GLTFLoader();
            const gltf = await new Promise((resolve, reject) => {
                loader.load(modelPath, resolve, undefined, reject);
            });
            object3D = gltf.scene;
        } else {
            throw new Error('不支持的模型格式');
        }
        
        // 解析层级结构
        const structure = parseModelStructure(object3D);
        
        // 显示结构弹窗
        showModelStructureModal(model, structure);
        
    } catch (error) {
        console.error('❌ 加载模型结构失败:', error);
        window.showManageMessage('加载失败: ' + error.message, 'error');
    }
};

// ============ 工具函数 ============

/**
 * 获取模型格式图标
 */
function getModelFormatIcon(format) {
    // 处理 undefined 或 null
    if (!format) {
        return '📦';
    }
    
    const iconMap = {
        'glb': '🎨',
        'gltf': '🎭',
        'obj': '🗿',
        'fbx': '🎪',
        'stl': '🔷'
    };
    return iconMap[format.toLowerCase()] || '📦';
}

// ============ 文件上传功能 ============

/**
 * 启动两阶段上传流程
 */
window.startTwoStageUpload = async function() {
    const currentProject = projectManager.getCurrentProject();
    if (!currentProject) {
        alert('请先选择一个项目');
        return;
    }
    
    try {
        const uploadManager = getTwoStageUploadManager(currentProject.id, currentProject.path);
        await uploadManager.startUpload();
    } catch (error) {
        console.error('❌ 启动两阶段上传失败:', error);
        alert('启动上传流程失败：' + error.message);
    }
};

/**
 * 启动节点模型组上传流程
 */
window.startNodeGroupUpload = async function() {
    const currentProject = projectManager.getCurrentProject();
    if (!currentProject) {
        alert('请先选择一个项目');
        return;
    }
    
    try {
        const uploadManager = getTwoStageUploadManager(currentProject.id, currentProject.path, 'node');
        await uploadManager.startUpload();
    } catch (error) {
        console.error('❌ 启动节点模型组上传失败:', error);
        alert('启动上传流程失败：' + error.message);
    }
};

/**
 * 打开上传模型对话框（旧版，保留兼容）
 */
window.uploadModelDialog = function() {
    const input = document.getElementById('model-upload-input');
    if (!input) {
        console.error('❌ 未找到文件上传input元素');
        alert('文件上传功能未初始化，请刷新页面重试');
        return;
    }
    input.click();
};

/**
 * 初始化文件选择监听器
 */
function initFileUploadListener() {
    const input = document.getElementById('model-upload-input');
    if (!input) {
        console.error('❌ 未找到文件上传input元素');
        return;
    }
    
    // 移除旧的监听器（如果有）
    const newInput = input.cloneNode(true);
    input.parentNode.replaceChild(newInput, input);
    
    newInput.addEventListener('change', async (e) => {
        const files = Array.from(e.target.files);
        if (files.length === 0) return;
        
        try {
            const currentProject = projectManager.getCurrentProject();
            if (!currentProject) {
                alert('请先选择一个项目');
                newInput.value = '';
                return;
            }
            
            // 检查文件大小（50MB限制）
            const maxSize = 50 * 1024 * 1024;
            const validFiles = [];
            const invalidFiles = [];
            
            for (const file of files) {
                if (file.size > maxSize) {
                    invalidFiles.push(file.name);
                } else {
                    validFiles.push(file);
                }
            }
            
            if (invalidFiles.length > 0) {
                alert(`以下文件超过50MB限制，已跳过：\n${invalidFiles.join('\n')}`);
            }
            
            if (validFiles.length === 0) {
                newInput.value = '';
                return;
            }
            
            // ⭐ 不再自动清空队列，允许累积添加
            // 如果队列中只有已完成（success/error）的文件，可以选择清空
            const hasOnlyCompletedItems = uploadQueue.length > 0 && 
                uploadQueue.every(item => item.status === 'success' || item.status === 'error');
            
            if (hasOnlyCompletedItems) {
                const shouldClear = confirm('队列中有已完成的文件，是否清空队列重新开始？\n\n点击"确定"清空队列\n点击"取消"继续添加到现有队列');
                if (shouldClear) {
                    uploadQueue = [];
                }
            }
            
            // 检测并收集重复文件
            const duplicateFiles = [];
            const newFiles = [];
            
            // 添加文件到队列（修复文件名编码）
            for (const file of validFiles) {
                // 修复文件名编码（从源头保证正确）
                let fileName = file.name;
                
                // 检测并修复文件名乱码
                const fixedFileName = fixStringEncoding(fileName);
                
                // 如果文件名被修复，创建新的File对象
                let processedFile = file;
                if (fixedFileName !== fileName) {
                    try {
                        processedFile = new File([file], fixedFileName, {
                            type: file.type,
                            lastModified: file.lastModified
                        });
                        fileName = fixedFileName;
                    } catch (error) {
                        console.warn('创建新File对象失败，使用原始文件:', error);
                    }
                }
                
                // ⭐ 检测队列中是否已存在同名文件
                const isDuplicate = uploadQueue.some(item => item.file.name === processedFile.name);
                
                if (isDuplicate) {
                    duplicateFiles.push(fileName);
                    console.warn(`⚠️ 队列中已存在同名文件: ${fileName}`);
                } else {
                    const queueItem = {
                        id: Date.now() + Math.random(),
                        file: processedFile,
                        name: fileName.replace(/\.[^/.]+$/, ''),
                        description: '',
                        status: 'pending', // pending, uploading, success, error
                        progress: 0,
                        error: null
                    };
                    uploadQueue.push(queueItem);
                    newFiles.push(fileName);
                }
            }
            
            // 显示重复文件提示
            if (duplicateFiles.length > 0) {
                let message = `⚠️ 以下文件已在队列中，已自动跳过：\n\n${duplicateFiles.join('\n')}`;
                if (newFiles.length > 0) {
                    message += `\n\n✅ 已成功添加 ${newFiles.length} 个新文件到队列`;
                }
                alert(message);
            }
            
            // 显示上传队列弹窗
            showUploadQueue();
            
        } catch (error) {
            console.error('❌ 添加文件出错:', error);
        } finally {
            newInput.value = '';
        }
    });
}

/**
 * 显示上传队列弹窗
 */
function showUploadQueue() {
    const modal = document.getElementById('upload-queue-modal');
    if (!modal) {
        console.error('❌ 未找到上传队列弹窗元素');
        return;
    }
    modal.classList.add('active');
    // 如果队列中没有已完成的文件，隐藏完成提示
    if (!uploadQueue.some(item => item.status === 'success' || item.status === 'error')) {
        const completeMessage = document.getElementById('upload-complete-message');
        if (completeMessage) {
            completeMessage.style.display = 'none';
        }
    }
    
    updateQueueList();
}

/**
 * 关闭上传队列弹窗
 */
window.closeUploadQueue = async function() {
    if (isUploading) {
        const confirmed = await window.customConfirm('正在上传中，确定要关闭吗？', '确认关闭');
        if (!confirmed) {
            return;
        }
        // 取消当前上传
        if (uploadXHR) {
            uploadXHR.abort();
            uploadXHR = null;
        }
    }
    document.getElementById('upload-queue-modal').classList.remove('active');
    
    // 如果有上传成功的文件，刷新模型库管理列表和侧边栏
    if (uploadQueue.some(item => item.status === 'success')) {
        const manageModal = document.getElementById('model-library-manage-modal');
        if (manageModal && manageModal.classList.contains('active')) {
            await loadModelManageList();
        }
        // 同步刷新模型库侧边栏
        await window.refreshModelLibraryPanel();
    }
};

/**
 * 更新队列列表显示
 */
function updateQueueList() {
    const listContainer = document.getElementById('upload-queue-list');
    const countEl = document.getElementById('queue-count');
    const startBtn = document.getElementById('start-upload-btn');
    
    if (!listContainer || !countEl || !startBtn) {
        console.error('❌ 队列UI元素未找到:', {
            listContainer: !!listContainer,
            countEl: !!countEl,
            startBtn: !!startBtn
        });
        return;
    }
    countEl.textContent = uploadQueue.length;
    
    if (uploadQueue.length === 0) {
        listContainer.innerHTML = '<div class="queue-empty">暂无文件，请选择文件添加到队列</div>';
        startBtn.disabled = true;
        return;
    }
    
    startBtn.disabled = isUploading || uploadQueue.every(item => item.status === 'success');
    listContainer.innerHTML = uploadQueue.map((item, index) => {
        const formatIcon = getModelFormatIcon(item.file.name.split('.').pop());
        let statusText = '等待中';
        let statusClass = '';
        
        if (item.status === 'uploading') {
            statusText = '上传中';
            statusClass = 'uploading';
        } else if (item.status === 'success') {
            statusText = '✅ 已完成';
            statusClass = 'success';
        } else if (item.status === 'error') {
            statusText = '❌ 失败';
            statusClass = 'error';
        }
        
        // 根据状态显示不同的进度条
        let progressBarHtml = '';
        if (item.status === 'uploading') {
            progressBarHtml = `
                <div class="queue-item-progress">
                    <div class="queue-item-progress-bar">
                        <div class="queue-item-progress-fill" style="width: ${item.progress}%"></div>
                    </div>
                    <div class="queue-item-progress-text">${item.progress}%</div>
                </div>
            `;
        } else if (item.status === 'success') {
            progressBarHtml = `
                <div class="queue-item-progress">
                    <div class="queue-item-progress-bar success">
                        <div class="queue-item-progress-fill" style="width: 100%"></div>
                    </div>
                    <div class="queue-item-progress-text">100%</div>
                </div>
            `;
        } else if (item.status === 'error') {
            progressBarHtml = `
                <div class="queue-item-progress">
                    <div class="queue-item-progress-bar error">
                        <div class="queue-item-progress-fill" style="width: 0%"></div>
                    </div>
                </div>
            `;
        }
        
        return `
            <div class="queue-item ${statusClass}" id="queue-item-${item.id}">
                <div class="queue-item-number">${index + 1}</div>
                <div class="queue-item-icon">${formatIcon}</div>
                <div class="queue-item-info">
                    <div class="queue-item-name" title="${item.file.name}">${item.file.name}</div>
                    <div class="queue-item-meta">
                        <span>${formatFileSize(item.file.size)}</span>
                        ${item.error ? `<span style="color: #f44336;">错误: ${item.error}</span>` : ''}
                    </div>
                    ${progressBarHtml}
                </div>
                <div class="queue-item-status ${statusClass}">${statusText}</div>
                ${item.status === 'pending' ? `<button class="queue-item-remove" onclick="removeFromQueue(${index})">删除</button>` : ''}
            </div>
        `;
    }).join('');
}

/**
 * 从队列中删除文件
 */
window.removeFromQueue = function(index) {
    if (isUploading) {
        alert('正在上传中，无法删除');
        return;
    }
    
    const removed = uploadQueue.splice(index, 1)[0];
    updateQueueList();
};

/**
 * 清空队列
 */
window.clearUploadQueue = async function() {
    if (isUploading) {
        window.showManageMessage('正在上传中，无法清空队列', 'error');
        return;
    }
    
    if (uploadQueue.length === 0) return;
    
    const pendingCount = uploadQueue.filter(item => item.status === 'pending').length;
    const completedCount = uploadQueue.filter(item => item.status === 'success' || item.status === 'error').length;
    
    let confirmed = false;
    if (completedCount > 0 && pendingCount > 0) {
        const message = `队列中有 ${pendingCount} 个待上传文件和 ${completedCount} 个已完成文件。\n\n点击"确定"清空所有文件\n点击"取消"保留已完成的文件`;
        confirmed = await window.customConfirm(message, '清空队列');
        
        if (confirmed) {
            uploadQueue = [];
        } else {
            // 只保留已完成的文件
            uploadQueue = uploadQueue.filter(item => item.status === 'success' || item.status === 'error');
        }
    } else {
        confirmed = await window.customConfirm(`确定要清空队列中的 ${uploadQueue.length} 个文件吗？`, '清空队列');
        if (!confirmed) {
            return;
        }
        uploadQueue = [];
    }
    
    // 隐藏完成提示
    const completeMessage = document.getElementById('upload-complete-message');
    if (completeMessage) {
        completeMessage.style.display = 'none';
    }
    
    updateQueueList();
};

/**
 * 添加更多文件
 */
window.addMoreFiles = function() {
    if (isUploading) {
        window.showManageMessage('正在上传中，请等待完成后再添加', 'error');
        return;
    }
    const input = document.getElementById('model-upload-input');
    if (!input) {
        console.error('❌ 未找到文件上传input元素');
        return;
    }
    input.click();
};

/**
 * 显示上传完成提示
 */
function showUploadCompleteMessage(successCount, errorCount) {
    const messageEl = document.getElementById('upload-complete-message');
    const textEl = document.getElementById('upload-complete-text');
    const iconEl = messageEl.querySelector('.complete-icon');
    
    if (!messageEl || !textEl || !iconEl) return;
    
    // 移除可能存在的error类
    messageEl.classList.remove('error');
    
    if (errorCount === 0) {
        // 全部成功
        iconEl.textContent = '🎉';
        textEl.innerHTML = `<strong>上传完成！</strong><br>所有文件上传成功，共 ${successCount} 个文件`;
    } else if (successCount === 0) {
        // 全部失败
        messageEl.classList.add('error');
        iconEl.textContent = '❌';
        textEl.innerHTML = `<strong>上传失败！</strong><br>所有文件上传失败，共 ${errorCount} 个文件`;
    } else {
        // 部分成功
        iconEl.textContent = '⚠️';
        textEl.innerHTML = `<strong>上传完成！</strong><br>成功 ${successCount} 个，失败 ${errorCount} 个`;
    }
    
    // 显示提示（使用绝对定位，不影响布局）
    messageEl.style.display = 'flex';
}

/**
 * 开始批量上传
 */
window.startBatchUpload = async function() {
    if (isUploading) {
        alert('正在上传中...');
        return;
    }
    
    // 只上传pending状态的文件
    const pendingItems = uploadQueue.filter(item => item.status === 'pending');
    if (pendingItems.length === 0) {
        alert('没有待上传的文件');
        return;
    }
    
    const currentProject = projectManager.getCurrentProject();
    if (!currentProject) {
        alert('未选择项目');
        return;
    }
    
    isUploading = true;
    document.getElementById('start-upload-btn').disabled = true;
    document.getElementById('add-more-btn').disabled = true;
    document.getElementById('current-upload-progress').style.display = 'block';
    
    // 隐藏之前的完成提示
    const completeMessage = document.getElementById('upload-complete-message');
    if (completeMessage) {
        completeMessage.style.display = 'none';
    }
    
    // 统计已有的成功和失败数量
    const alreadySuccess = uploadQueue.filter(item => item.status === 'success').length;
    const alreadyFailed = uploadQueue.filter(item => item.status === 'error').length;
    let successCount = 0;
    let errorCount = 0;
    
    for (const item of pendingItems) {
        try {
            await uploadSingleFile(item, currentProject.id);
            successCount++;
            
        } catch (error) {
            console.error('❌ 上传失败:', error.message);
            errorCount++;
        }
        
        // 每次上传后更新队列显示
        updateQueueList();
    }
    isUploading = false;
    document.getElementById('add-more-btn').disabled = false;
    document.getElementById('current-upload-progress').style.display = 'none';
    
    // 更新最终状态
    updateQueueList();
    
    // 如果有成功上传的文件，刷新模型库管理列表和侧边栏
    if (successCount > 0) {
        // 检查模型库管理弹窗是否打开
        const manageModal = document.getElementById('model-library-manage-modal');
        if (manageModal && manageModal.classList.contains('active')) {
            await loadModelManageList();
        }
        // 同步刷新模型库侧边栏
        await window.refreshModelLibraryPanel();
    }
    
    // 显示完成提示（在窗口内）
    showUploadCompleteMessage(successCount, errorCount);
};

/**
 * 上传单个文件
 */
async function uploadSingleFile(queueItem, projectId) {
    return new Promise((resolve, reject) => {
        // 更新状态
        queueItem.status = 'uploading';
        queueItem.progress = 0;
        updateQueueList();
        
        // 更新当前上传显示
        document.getElementById('current-upload-icon').textContent = getModelFormatIcon(queueItem.file.name.split('.').pop());
        document.getElementById('current-upload-name').textContent = queueItem.file.name;
        document.getElementById('current-upload-size').textContent = formatFileSize(queueItem.file.size);
        document.getElementById('current-upload-status').textContent = '上传中...';
        document.getElementById('current-upload-percent').textContent = '0%';
        document.getElementById('current-upload-fill').style.width = '0%';
        
        // 创建FormData
        const formData = new FormData();
        
        // 显式指定文件名，避免浏览器重新编码
        const fileName = queueItem.file.name;
        formData.append('model', queueItem.file, fileName);
        formData.append('name', queueItem.name);
        formData.append('description', queueItem.description);
        
        // ⭐ 关键：额外传递修复后的正确文件名
        // 后端优先使用这个字段，避免依赖multer可能被编码的originalname
        formData.append('originalFileName', fileName);
        // 创建XMLHttpRequest
        uploadXHR = new XMLHttpRequest();
        
        // 上传进度
        uploadXHR.upload.addEventListener('progress', (e) => {
            if (e.lengthComputable) {
                const percent = Math.round((e.loaded / e.total) * 100);
                queueItem.progress = percent;
                
                document.getElementById('current-upload-percent').textContent = percent + '%';
                document.getElementById('current-upload-fill').style.width = percent + '%';
                
                updateQueueList();
            }
        });
        
        // 上传完成
        uploadXHR.addEventListener('load', () => {
            try {
                const result = JSON.parse(uploadXHR.responseText);
                
                if (uploadXHR.status === 200 && result.success) {
                    queueItem.status = 'success';
                    queueItem.progress = 100;
                    resolve(result.data);
                } else {
                    throw new Error(result.error || '上传失败');
                }
            } catch (error) {
                queueItem.status = 'error';
                queueItem.error = error.message;
                reject(error);
            }
        });
        
        // 上传错误
        uploadXHR.addEventListener('error', () => {
            queueItem.status = 'error';
            queueItem.error = '网络错误';
            reject(new Error('网络错误'));
        });
        
        // 上传中止
        uploadXHR.addEventListener('abort', () => {
            queueItem.status = 'error';
            queueItem.error = '已取消';
            reject(new Error('上传已取消'));
        });
        
        // 发送请求
        uploadXHR.open('POST', `/api/projects/${projectId}/models/upload`);
        uploadXHR.send(formData);
    });
}

/**
 * 取消上传
 */
window.cancelUpload = function() {
    if (uploadXHR) {
        uploadXHR.abort();
        uploadXHR = null;
    }
    document.getElementById('upload-progress-modal').classList.remove('active');
};

// ============ 模型依赖查看 ============

/**
 * 获取项目的场景列表（用于查看依赖时显示场景名称）
 */
async function getProjectScenes(projectId) {
    try {
        const response = await fetch(`/api/projects/${projectId}`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error(result.error || '获取场景列表失败');
        }
        
        return result.data.scenes || [];
    } catch (error) {
        console.error('❌ 获取场景列表失败:', error);
        return [];
    }
}

/**
 * 获取场景的节点信息（包括名称和路径）
 */
async function getSceneNodeInfo(projectId, sceneId, nodeId) {
    try {
        const response = await fetch(`/api/projects/${projectId}/scenes/${sceneId}/tree`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error('获取场景数据失败');
        }
        
        // 递归查找节点并构建路径
        function findNodeWithPath(node, targetId, path = []) {
            if (!node) return null;
            
            const currentPath = [...path, node.name];
            
            if (node.id === targetId) {
                return {
                    node: node,
                    path: currentPath
                };
            }
            
            if (node.children && node.children.length > 0) {
                for (const child of node.children) {
                    const found = findNodeWithPath(child, targetId, currentPath);
                    if (found) return found;
                }
            }
            
            return null;
        }
        
        const result_info = findNodeWithPath(result.data.root, nodeId);
        
        if (result_info) {
            return {
                name: result_info.node.name,
                path: result_info.path.join(' > ')
            };
        } else {
            return {
                name: nodeId,
                path: nodeId
            };
        }
        
    } catch (error) {
        console.error('❌ 获取节点信息失败:', error);
        return {
            name: nodeId,
            path: nodeId
        };
    }
}

/**
 * 查看模型依赖详情
 */
window.viewModelDependencies = async function(modelId, modelType) {
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            window.showManageMessage('未选择项目', 'error');
            return;
        }
        
        // 如果没有传递modelType，尝试从DOM元素获取
        if (!modelType) {
            const modelItem = document.querySelector(`[data-model-id="${modelId}"]`);
            modelType = modelItem?.dataset.modelType || 'model';
        }
        
        // 构建API URL，添加type参数（如果是node类型）
        const typeParam = modelType === 'node' ? '?type=node' : '';
        
        // 获取单个模型的详细信息（包含usedByNodesDetails）
        const response = await fetch(`/api/projects/${currentProject.id}/models/${modelId}${typeParam}`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error('获取模型信息失败');
        }
        
        const model = result.data;
        if (!model) {
            window.showManageMessage('模型不存在', 'error');
            return;
        }
        
        // 使用后端返回的详细依赖信息
        const usedByNodesDetails = model.usedByNodesDetails || [];
        
        if (usedByNodesDetails.length === 0) {
            window.showManageMessage('该模型暂无引用', 'info');
            return;
        }
        
        // 获取场景列表（用于显示场景名称）
        const scenes = await getProjectScenes(currentProject.id);
        const sceneMap = new Map(scenes.map(s => [s.id, s.name]));
        
        // 显示依赖详情弹窗（使用后端提供的详细信息）
        await showDependencyModal(model, usedByNodesDetails, sceneMap);
        
    } catch (error) {
        console.error('❌ 查看模型依赖失败:', error);
        window.showManageMessage('查看失败: ' + error.message, 'error');
    }
};

/**
 * 显示依赖详情弹窗
 */
async function showDependencyModal(model, usedByNodesDetails, sceneMap) {
    // 创建或获取弹窗元素
    let modal = document.getElementById('model-dependency-modal');
    if (!modal) {
        modal = document.createElement('div');
        modal.id = 'model-dependency-modal';
        modal.className = 'modal';
        modal.innerHTML = `
            <div class="modal-content dependency-modal-content">
                <div class="modal-header">
                    <h2 id="dependency-modal-title">模型依赖详情</h2>
                    <button class="modal-close" onclick="closeDependencyModal()">×</button>
                </div>
                <div class="modal-body dependency-modal-body">
                    <div id="dependency-list"></div>
                </div>
            </div>
        `;
        document.body.appendChild(modal);
    }
    
    // 准备依赖项数据（直接使用后端提供的详细信息）
    const dependencyItems = usedByNodesDetails.map((detail) => {
        const sceneName = sceneMap.get(detail.sceneId) || detail.sceneId;
        
        return {
            sceneId: detail.sceneId,
            sceneName: sceneName,
            nodeId: detail.nodeId,
            nodeName: detail.nodeName,
            nodePath: detail.nodePath
        };
    });
    
    // 设置标题（包含引用计数）
    const titleEl = document.getElementById('dependency-modal-title');
    titleEl.innerHTML = `
        <span class="dependency-modal-title-text">模型依赖详情 - ${model.name}</span>
        <span class="dependency-modal-count">共 ${dependencyItems.length} 个引用</span>
    `;
    
    // 显示弹窗
    modal.classList.add('active');
    
    // 渲染依赖列表
    const listEl = document.getElementById('dependency-list');
    listEl.innerHTML = `
        <div class="dependency-items">
            ${dependencyItems.map((item, index) => `
                <div class="dependency-item">
                    <div class="dependency-item-number">${index + 1}</div>
                    <div class="dependency-item-content">
                        <div class="dependency-row">
                            <span class="dependency-label">场景</span>
                            <span class="dependency-value">${item.sceneName}</span>
                            <span class="dependency-id">${item.sceneId}</span>
                        </div>
                        
                        <div class="dependency-row">
                            <span class="dependency-label">节点</span>
                            <span class="dependency-value">${item.nodeName}</span>
                            <span class="dependency-id">${item.nodeId}</span>
                        </div>
                        
                        <div class="dependency-row">
                            <span class="dependency-label">路径</span>
                            <span class="dependency-path">${item.nodePath}</span>
                        </div>
                    </div>
                </div>
            `).join('')}
        </div>
    `;
}

/**
 * 关闭依赖详情弹窗
 */
window.closeDependencyModal = function() {
    const modal = document.getElementById('model-dependency-modal');
    if (modal) {
        modal.classList.remove('active');
    }
};

// ============ 模型编辑和删除 ============

/**
 * 编辑模型信息
 */
window.editModelInfo = async function(modelId, modelType) {
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            window.showManageMessage('未选择项目', 'error');
            return;
        }
        
        // 如果没有传递modelType，尝试从DOM元素获取
        if (!modelType) {
            const modelItem = document.querySelector(`[data-model-id="${modelId}"]`);
            modelType = modelItem?.dataset.modelType || 'model';
        }
        
        // 构建API URL，添加type参数（如果是node类型）
        const typeParam = modelType === 'node' ? '?type=node' : '';
        
        // 获取模型列表，找到当前模型
        const response = await fetch(`/api/projects/${currentProject.id}/models${typeParam}`);
        const result = await response.json();
        
        if (!result.success) {
            throw new Error('获取模型信息失败');
        }
        
        const model = result.data.find(m => m.id === modelId);
        if (!model) {
            window.showManageMessage('模型不存在', 'error');
            return;
        }
        // 使用自定义编辑对话框，传递当前标签
        const editResult = await window.showEditModelDialog(model.name, model.description, model.scale || 0.01, model.tags || []);
        // 如果用户取消或数据未变化，则返回
        if (!editResult) {
            return;
        }
        
        // 检查是否有变化（包括标签）
        const tagsChanged = JSON.stringify(editResult.tags) !== JSON.stringify(model.tags || []);
        if (editResult.name === model.name && 
            editResult.description === model.description && 
            editResult.scale === (model.scale || 0.01) &&
            !tagsChanged) {
            return;
        }
        
        // 更新模型信息
        const updateUrl = `/api/projects/${currentProject.id}/models/${modelId}${typeParam}`;
        const updateBody = {
            name: editResult.name,
            scale: editResult.scale,
            description: editResult.description,
            tags: editResult.tags
        };
        const updateResponse = await fetch(updateUrl, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(updateBody)
        });
        const updateResult = await updateResponse.json();
        if (!updateResult.success) {
            throw new Error(updateResult.error || '更新失败');
        }
        window.showManageMessage('模型信息更新成功！', 'success');
        
        // 根据modelType刷新对应的列表
        if (modelType === 'node') {
            // 刷新节点库管理列表
            await loadNodeManageList();
            // 刷新节点库侧边栏
            const panel = document.getElementById('model-library-panel-content');
            if (panel && window.populateNodeList) {
                await window.populateNodeList();
            }
        } else {
            // 刷新模型库管理列表
            await loadModelManageList();
            // 刷新模型库侧边栏
            const panel = document.getElementById('model-library-panel-content');
            if (panel && window.populateModelList) {
                await window.populateModelList();
            }
        }
    } catch (error) {
        console.error('❌ 编辑模型失败:', error);
        console.error('错误堆栈:', error.stack);
        window.showManageMessage('编辑失败: ' + error.message, 'error');
    }
};

/**
 * 删除项目模型（带完整依赖分析）
 */
window.deleteProjectModel = async function(modelId, modelType) {
    // 防止重复点击
    if (window.isDeletingModel) {
        console.warn('⚠️ 正在删除模型中，请勿重复点击');
        return;
    }
    
    // 设置删除中标志（在显示对话框前就设置，防止快速多次点击）
    window.isDeletingModel = true;
    
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            window.showManageMessage('未选择项目', 'error');
            window.isDeletingModel = false;
            return;
        }
        
        // 如果没有传递modelType，尝试从DOM元素获取
        if (!modelType) {
            const modelItem = document.querySelector(`[data-model-id="${modelId}"]`);
            modelType = modelItem?.dataset.modelType || 'model';
        }
        
        // 构建API URL，添加type参数（如果是node类型）
        const typeParam = modelType === 'node' ? '?type=node' : '';
        
        // 1. 获取模型详细信息（包括依赖）
        const modelResponse = await fetch(`/api/projects/${currentProject.id}/models/${modelId}${typeParam}`);
        const modelResult = await modelResponse.json();
        
        if (!modelResult.success) {
            throw new Error(modelResult.error || '获取模型信息失败');
        }
        
        const model = modelResult.data;
        const dependencies = model.usedByNodesDetails || [];
        
        // 2. 如果有依赖，获取每个节点的完整子树信息
        let allAffectedNodes = [];
        let allAffectedModels = new Map(); // modelId -> count
        
        if (dependencies.length > 0) {
            for (const dep of dependencies) {
                try {
                    const subtreeResponse = await fetch(
                        `/api/projects/${currentProject.id}/scenes/${dep.sceneId}/nodes/${dep.nodeId}/subtree`
                    );
                    
                    if (!subtreeResponse.ok) {
                        console.warn(`   ⚠️ 获取子树失败: HTTP ${subtreeResponse.status} - 节点已被删除，跳过`);
                        // 节点已被删除，后端会自动清理无效引用，直接跳过
                        continue;
                    }
                    
                    const subtreeResult = await subtreeResponse.json();
                    if (subtreeResult.success) {
                        const subtreeData = subtreeResult.data;
                        allAffectedNodes.push({
                            sceneId: dep.sceneId,
                            rootNodeId: dep.nodeId,
                            rootNodeName: dep.nodeName,
                            rootNodePath: dep.nodePath,
                            subtreeNodes: subtreeData.nodes
                        });
                        // 统计受影响的模型（从components中提取modelId）
                        for (const node of subtreeData.nodes) {
                            const nodeModelId = getNodeModelId(node);
                            if (nodeModelId) {
                                const count = allAffectedModels.get(nodeModelId) || 0;
                                allAffectedModels.set(nodeModelId, count + 1);
                            }
                        }
                    } else {
                        console.warn(`   ⚠️ 子树API返回失败: ${subtreeResult.error} - 节点已被删除，跳过`);
                        // 节点已被删除，跳过
                        continue;
                    }
                } catch (error) {
                    console.error(`❌ 获取节点子树失败: ${dep.sceneId}/${dep.nodeId}`, error);
                    // 出错时跳过这个节点
                    continue;
                }
            }
        }
        
        // 移除当前要删除的模型（因为它会被删除）
        const currentModelCount = allAffectedModels.get(modelId);
        if (currentModelCount) {
        }
        allAffectedModels.delete(modelId);
        if (allAffectedModels.size > 0) {
            for (const [mid, count] of allAffectedModels) {
            }
        }
        
        // 3. 显示确认对话框（对话框内部会管理 isDeletingModel 标志）
        await showModelDeleteConfirmDialog(model, allAffectedNodes, allAffectedModels, modelType);
    } catch (error) {
        console.error('❌ 删除模型失败:', error);
        window.showManageMessage('删除失败: ' + error.message, 'error');
        // 出错时释放标志
        window.isDeletingModel = false;
    }
    // 注意：不在 finally 块释放标志，因为标志由对话框内部管理
    // 对话框会在用户点击确认/取消/关闭时释放标志
};

/**
 * 显示模型删除确认对话框（带完整依赖信息）
 */
async function showModelDeleteConfirmDialog(model, affectedNodes, affectedModels, modelType = 'model') {
    const currentProject = projectManager.getCurrentProject();
    
    // 重要说明：affectedModels 已经在 handleDeleteModel 中统计完成，并且已经移除了被删除的模型
    // 这里我们直接使用这个数据，不需要重复统计，以避免重复计数
    
    // 收集所有需要获取详细信息的模型ID
    const allModelIds = new Set();
    
    // 1. 添加被删除的模型本身（用于显示）
    allModelIds.add(model.id);
    
    // 2. 添加关联模型（其他模型）
    for (const [modelId] of affectedModels) {
        allModelIds.add(modelId);
    }
    
    // 批量获取所有模型的详细信息
    const modelDetailsMap = new Map();
    if (allModelIds.size > 0) {
        for (const modelId of allModelIds) {
            try {
                const modelResponse = await fetch(`/api/projects/${currentProject.id}/models/${modelId}`);
                const modelResult = await modelResponse.json();
                if (modelResult.success) {
                    modelDetailsMap.set(modelId, {
                        name: modelResult.data.name,
                        format: modelResult.data.format,
                        // 使用 affectedModels 中已经统计好的准确计数
                        count: affectedModels.get(modelId) || 0
                    });
                }
            } catch (error) {
                console.error(`获取模型信息失败: ${modelId}`, error);
                modelDetailsMap.set(modelId, {
                    name: modelId,
                    format: 'N/A',
                    count: affectedModels.get(modelId) || 0
                });
            }
        }
    }
    
    return new Promise((resolve) => {
        try {
            // 创建模态窗口
            const modal = document.createElement('div');
            modal.className = 'modal-overlay';
            modal.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0, 0, 0, 0.85);
                backdrop-filter: blur(8px);
                display: flex;
                justify-content: center;
                align-items: center;
                z-index: 999999;
            `;
            
            // ========== 统计数据 ==========
            // 注意：这里使用传入的 affectedModels（已在上层函数中正确统计并移除了被删除的模型）
            
            // 1. 统计场景数量
            const totalScenes = new Set(affectedNodes.map(item => item.sceneId)).size;
            
            // 2. 统计将被删除的节点总数（包括根节点和所有子孙节点）
            const totalNodes = affectedNodes.reduce((sum, item) => sum + item.subtreeNodes.length, 0);
            
            // 3. 统计关联模型数量（已排除被删除的模型，这个数据从上层传入，已确保准确）
            const actualAffectedModelsCount = affectedModels.size;
            
            // 4. 统计含有模型引用的节点数量（遍历所有将被删除的节点）
            let totalNodesWithModels = 0;
            let nodesUsingDeletedModel = 0;  // 引用被删除模型的节点数
            let nodesUsingOtherModels = 0;   // 引用其他模型的节点数
            
            for (const nodeGroup of affectedNodes) {
                for (const node of nodeGroup.subtreeNodes) {
                    const nodeModelId = getNodeModelId(node);
                    if (nodeModelId) {
                        totalNodesWithModels++;
                        
                        if (nodeModelId === model.id) {
                            nodesUsingDeletedModel++;
                        } else {
                            nodesUsingOtherModels++;
                        }
                    }
                }
            }
        let contentHTML = `
            <div class="modal-content delete-confirm-modal" style="max-width: 1200px; width: 95vw; max-height: 90vh; display: flex; flex-direction: column;">
                <div class="modal-header" style="flex-shrink: 0; padding: 15px 20px; display: flex; align-items: center; justify-content: space-between; gap: 20px;">
                    <div style="flex: 1; display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;">
                        <h3 style="margin: 0; white-space: nowrap;">🗑 删除模型：${model.name}</h3>
                        <div style="display: flex; align-items: center; gap: 6px;">
                            <span style="font-size: 13px;">⚠️</span>
                            <span style="font-size: 11px; color: rgba(255, 255, 255, 0.65);">此操作将删除模型及其所有使用节点（包括子孙节点）</span>
                        </div>
                    </div>
                    <button class="modal-close" onclick="this.closest('.modal-overlay').remove()" style="flex-shrink: 0;">✕</button>
                </div>
                
        `;
        
        // 显示关联模型详情（只显示其他模型，不包括被删除的模型）
        // affectedModels 中已经排除了被删除的模型，所以这里直接使用
        const otherAffectedModels = Array.from(affectedModels.entries());
        
        if (otherAffectedModels.length > 0) {
            contentHTML += `
                <!-- 关联模型区域 -->
                <div class="modal-overview" style="flex-shrink: 0; padding: 8px 15px 6px 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
                    <div class="affected-models-section">
                        <div style="font-size: 10px; font-weight: 600; color: rgba(255, 255, 255, 0.6); margin-bottom: 5px; display: flex; align-items: center; gap: 4px;">
                            <span style="font-size: 11px;">🎨</span>
                            <span>需更新引用的模型：</span>
                        </div>
                        <div style="display: flex; flex-wrap: wrap; gap: 5px;">
            `;
            
            // 使用更紧凑的横向布局显示关联模型
            for (const [modelId, count] of otherAffectedModels) {
                const modelInfo = modelDetailsMap.get(modelId);
                const modelName = modelInfo ? modelInfo.name : modelId;
                const modelFormat = modelInfo ? modelInfo.format.toUpperCase() : 'N/A';
                
                contentHTML += `
                            <div style="background: rgba(102, 126, 234, 0.08); border: 1px solid rgba(102, 126, 234, 0.18); border-radius: 4px; padding: 4px 8px; display: inline-flex; align-items: center; gap: 4px;">
                                <span style="color: rgba(255, 255, 255, 0.9); font-size: 11px; font-weight: 500;">${modelName}</span>
                                <span style="color: rgba(255, 255, 255, 0.35); font-size: 9px;">${modelFormat}</span>
                                <span style="background: rgba(253, 203, 110, 0.25); padding: 1px 5px; border-radius: 2px; color: #fdcb6e; font-weight: 600; font-size: 9px;">-${count}</span>
                            </div>
                `;
            }
            
            contentHTML += `
                        </div>
                    </div>
                </div>
            `;
        }
        
        // 开始节点详情区域
        contentHTML += `
                <!-- 可滚动的节点详情区域 -->
                <div class="modal-body" style="flex: 1; overflow-y: auto; padding: 8px 15px 12px 15px;">
        `;
        
        if (affectedNodes.length > 0) {
            // 获取场景信息
            const scenes = projectManager.getScenes();
            const sceneMap = new Map(scenes.map(s => [s.id, s.name]));
            
            // 按场景分组节点
            const nodesByScene = new Map();
            for (const item of affectedNodes) {
                if (!nodesByScene.has(item.sceneId)) {
                    nodesByScene.set(item.sceneId, []);
                }
                nodesByScene.get(item.sceneId).push(item);
            }
            
            contentHTML += `
                <div class="affected-nodes-section">
            `;
            
            // 遍历每个场景
            let sceneIndex = 0;
            for (const [sceneId, sceneNodes] of nodesByScene) {
                const sceneName = sceneMap.get(sceneId) || '未命名场景';
                const sceneNodeCount = sceneNodes.reduce((sum, item) => sum + item.subtreeNodes.length, 0);
                
                // 计算该场景引用的资源数（去重）
                const sceneModels = new Set();
                for (const item of sceneNodes) {
                    for (const node of item.subtreeNodes) {
                        const nodeModelId = getNodeModelId(node);
                        if (nodeModelId) {
                            sceneModels.add(nodeModelId);
                        }
                    }
                }
                const sceneModelCount = sceneModels.size;
                
                const collapseId = `scene-collapse-${sceneIndex}`;
                
                contentHTML += `
                    <div class="scene-group" style="margin-bottom: 8px;">
                        <!-- 场景展开栏 -->
                        <div onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.querySelector('.collapse-icon').textContent = this.nextElementSibling.style.display === 'none' ? '▶' : '▼';" 
                             style="background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(102, 126, 234, 0.08) 100%); border: 1px solid rgba(102, 126, 234, 0.3); border-radius: 5px; padding: 8px 12px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: space-between;"
                             onmouseenter="this.style.background='linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(102, 126, 234, 0.12) 100%)'; this.style.borderColor='rgba(102, 126, 234, 0.4)';"
                             onmouseleave="this.style.background='linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(102, 126, 234, 0.08) 100%)'; this.style.borderColor='rgba(102, 126, 234, 0.3)';">
                            <div style="display: flex; align-items: center; gap: 8px;">
                                <span class="collapse-icon" style="font-size: 10px; color: rgba(255, 255, 255, 0.6); transition: transform 0.2s;">▼</span>
                                <span style="font-size: 13px;">🏞️</span>
                                <span style="font-weight: 600; color: rgba(255, 255, 255, 0.95); font-size: 12px;">${sceneName}</span>
                                <span style="color: rgba(255, 255, 255, 0.4); font-size: 10px;">(${sceneId})</span>
                            </div>
                            <div style="display: flex; align-items: center; gap: 6px;">
                                <span style="background: rgba(250, 177, 160, 0.3); padding: 3px 8px; border-radius: 4px; font-size: 10px; color: #fab1a0; font-weight: 600;">
                                    ${sceneNodes.length}个根节点
                                </span>
                                <span style="background: rgba(255, 107, 107, 0.3); padding: 3px 8px; border-radius: 4px; font-size: 10px; color: #ff6b6b; font-weight: 600;">
                                    ${sceneNodeCount}个节点
                                </span>
                                ${sceneModelCount > 0 ? `<span style="background: rgba(253, 203, 110, 0.3); padding: 3px 8px; border-radius: 4px; font-size: 10px; color: #fdcb6e; font-weight: 600;">
                                    ${sceneModelCount}个资源
                                </span>` : ''}
                            </div>
                        </div>
                        
                        <!-- 该场景下的节点路径列表（可折叠） -->
                        <div id="${collapseId}" style="padding: 8px 0 0 0;">
                `;
                
                // 显示节点路径
                for (const item of sceneNodes) {
                    // 计算该子树的节点数和使用被删除模型的节点数
                    const totalNodesInSubtree = item.subtreeNodes.length;
                    const nodesUsingDeletedModel = item.subtreeNodes.filter(n => getNodeModelId(n) === model.id).length;
                    const nodesUsingOtherModels = item.subtreeNodes.filter(n => {
                        const mid = getNodeModelId(n);
                        return mid && mid !== model.id;
                    }).length;
                    
                    // 显示根节点路径
                    contentHTML += `
                        <div class="node-path-item" style="background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 107, 107, 0.15); border-radius: 5px; padding: 8px 10px; margin-bottom: 6px;">
                            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
                                <span style="font-size: 11px;">🗑</span>
                                <span style="color: rgba(255, 255, 255, 0.95); font-size: 11px; font-weight: 600;">${item.rootNodeName}</span>
                                <span style="background: rgba(255, 107, 107, 0.25); padding: 1px 5px; border-radius: 2px; color: #ff6b6b; font-weight: 600; font-size: 9px;">删除 ${totalNodesInSubtree}个节点</span>
                                ${nodesUsingDeletedModel > 0 ? `<span style="background: rgba(255, 107, 107, 0.25); padding: 1px 5px; border-radius: 2px; color: #ff6b6b; font-weight: 600; font-size: 9px;">${nodesUsingDeletedModel}个使用此模型</span>` : ''}
                                ${nodesUsingOtherModels > 0 ? `<span style="background: rgba(253, 203, 110, 0.25); padding: 1px 5px; border-radius: 2px; color: #fdcb6e; font-weight: 600; font-size: 9px;">${nodesUsingOtherModels}个使用其他模型</span>` : ''}
                            </div>
                            <div style="color: rgba(255, 255, 255, 0.5); font-size: 10px; font-family: 'Consolas', 'Monaco', monospace;">
                                路径: ${item.rootNodePath || '/'}
                            </div>
                    `;
                    
                    // 显示子节点中使用模型的节点（分组显示）
                    const nodesWithModels = item.subtreeNodes.filter(n => getNodeModelId(n));
                    if (nodesWithModels.length > 0) {
                        contentHTML += `
                            <div style="margin-top: 6px; padding-left: 10px; border-left: 2px solid rgba(255, 107, 107, 0.3);">
                                <div style="font-size: 9px; color: rgba(255, 255, 255, 0.4); margin-bottom: 3px;">受影响的模型节点：</div>
                        `;
                        
                        for (const node of nodesWithModels) {
                            const depth = node.depth || 0;
                            const indent = '  '.repeat(depth);
                            const nodeModelId = getNodeModelId(node);
                            const modelInfo = modelDetailsMap.get(nodeModelId);
                            const isDeletedModel = nodeModelId === model.id;
                            
                            contentHTML += `
                                <div style="padding: 2px 0; font-size: 10px; color: rgba(255, 255, 255, 0.7); font-family: 'Consolas', 'Monaco', monospace; display: flex; align-items: center; gap: 6px;">
                                    <span style="color: rgba(255, 255, 255, 0.3);">${indent}${depth > 0 ? '└─ ' : ''}</span>
                                    <span>${node.name || node.nodeName}</span>
                                    <span style="color: rgba(255, 255, 255, 0.5); font-size: 9px;">→</span>
                                <span style="background: ${isDeletedModel ? 'rgba(255, 107, 107, 0.15)' : 'rgba(102, 126, 234, 0.15)'}; 
                                                  padding: 1px 5px; border-radius: 2px; font-size: 9px; 
                                              color: ${isDeletedModel ? '#ff6b6b' : '#667eea'}; 
                                                  display: inline-flex; align-items: center; gap: 3px;">
                                        🎨 ${modelInfo ? modelInfo.name : nodeModelId}
                                        ${isDeletedModel ? '<span style="font-weight: 600;">🗑</span>' : ''}
                                </span>
                                </div>
                            `;
                        }
                        
                        contentHTML += `
                            </div>
                        `;
                    }
                    
                    contentHTML += `
                        </div>
                    `;
                }
                
                // 关闭场景下的节点列表
                contentHTML += `
                        </div>
                    </div>
                `;
                
                sceneIndex++;
            }
            
            // 关闭 affected-nodes-section
            contentHTML += `</div>`;
        } else {
            contentHTML += `
                <div style="background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; padding: 20px; text-align: center; color: rgba(255, 255, 255, 0.5);">
                    ✓ 此模型未被任何场景节点使用
                </div>
            `;
        }
        
        contentHTML += `
                </div>
                
                <div class="modal-footer" style="flex-shrink: 0; display: flex; gap: 10px; justify-content: flex-end; padding: 15px 20px; border-top: 1px solid rgba(255,255,255,0.1);">
                    <button class="modal-btn modal-btn-secondary cancel-delete-btn" style="min-width: 100px;">
                        取消
                    </button>
                    <button class="modal-btn modal-btn-danger delete-confirm-btn" style="min-width: 100px;">
                        确认删除
                    </button>
                </div>
            </div>
        `;
        modal.innerHTML = contentHTML;
        document.body.appendChild(modal);
        
        // 绑定取消按钮
        const cancelBtn = modal.querySelector('.cancel-delete-btn');
        if (cancelBtn) {
            cancelBtn.onclick = () => {
                window.isDeletingModel = false;
                modal.remove();
                resolve(false);
            };
        }
        
        // 绑定确认按钮
        const confirmBtn = modal.querySelector('.delete-confirm-btn');
        if (!confirmBtn) {
            console.error('❌ 找不到确认按钮！');
            window.isDeletingModel = false;
            return;
        }
        
        confirmBtn.onclick = async () => {
            // 防止重复点击确认按钮
            if (confirmBtn.disabled) {
                return;
            }
            
            confirmBtn.disabled = true;
            confirmBtn.textContent = '删除中...';
            
            try {
                // 执行删除
                await executeModelDeletion(model.id, affectedNodes, affectedModels, modelType);
                modal.remove();
                // 删除成功，释放标志
                window.isDeletingModel = false;
                resolve(true);
            } catch (error) {
                console.error('❌ 删除失败:', error);
                window.showManageMessage('删除失败: ' + error.message, 'error');
                confirmBtn.disabled = false;
                confirmBtn.textContent = '确认删除';
                // 删除失败，释放标志
                window.isDeletingModel = false;
            }
        };
        
        // 点击模态框外部关闭
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                window.isDeletingModel = false;
                modal.remove();
                resolve(false);
            }
        });
        
        } catch (error) {
            console.error('❌ showModelDeleteConfirmDialog 执行出错:', error);
            console.error('   - 错误堆栈:', error.stack);
            window.isDeletingModel = false;
            resolve(false);
        }
    });
}

/**
 * 执行模型删除（包括级联删除节点和更新依赖）
 */
async function executeModelDeletion(modelId, affectedNodes, affectedModels, modelType = 'model') {
    const currentProject = projectManager.getCurrentProject();
    const currentSceneId = window._sceneManager?.getCurrentSceneName();
    
    // 构建API URL，添加type参数（如果是node类型）
    const typeParam = modelType === 'node' ? '?type=node' : '';
    console.log(`🔧 executeModelDeletion - modelId: ${modelId}, modelType: ${modelType}, typeParam: ${typeParam}`);
    
    // 记录哪些场景受到影响
    const affectedScenes = new Set();
    
    // 🔧 过滤掉父子节点重复的情况：如果节点A是节点B的子孙，只保留B（最顶层的祖先）
    // 这样可以避免删除父节点后，再次尝试删除已被级联删除的子节点导致404错误
    const filteredNodes = [];
    const nodesToSkip = new Set(); // 需要跳过的节点ID（因为它们是其他节点的子孙）
    
    // 检查每个节点是否是其他节点的子孙
    for (let i = 0; i < affectedNodes.length; i++) {
        const nodeA = affectedNodes[i];
        const nodeAId = nodeA.rootNodeId;
        
        // 检查nodeA是否是其他节点的子孙
        let isDescendant = false;
        for (let j = 0; j < affectedNodes.length; j++) {
            if (i === j) continue; // 跳过自己
            
            const nodeB = affectedNodes[j];
            
            // 必须在同一个场景中才可能有父子关系
            if (nodeA.sceneId !== nodeB.sceneId) continue;
            
            // 检查nodeA是否在nodeB的子树中
            const isInSubtree = nodeB.subtreeNodes.some(n => n.id === nodeAId);
            if (isInSubtree) {
                isDescendant = true;
                nodesToSkip.add(nodeAId);
                break;
            }
        }
        
        if (!isDescendant) {
            filteredNodes.push(nodeA);
        }
    }
    
    // 1. 删除所有使用该模型的节点（统一使用后端API，确保引用正确更新）
    
    for (const item of filteredNodes) {
        // ✅ 统一使用后端API删除节点，确保：
        // 1. 节点数据被正确删除并保存
        // 2. 模型和材质的引用被自动更新
        // 3. 数据一致性得到保证
        const deleteResponse = await fetch(
            `/api/projects/${currentProject.id}/scenes/${item.sceneId}/nodes/${item.rootNodeId}`,
            { method: 'DELETE' }
        );
        
        const deleteResult = await deleteResponse.json();
        if (!deleteResult.success) {
            console.error(`❌ 删除节点失败: ${item.sceneId}/${item.rootNodeId}`);
            throw new Error(`删除节点失败: ${deleteResult.error}`);
        }
        
        // 记录受影响的场景
        affectedScenes.add(item.sceneId);
    }
    
    // 2. 删除模型本身
    const response = await fetch(`/api/projects/${currentProject.id}/models/${modelId}${typeParam}`, {
        method: 'DELETE'
    });
    
    const result = await response.json();
    
    if (!result.success) {
        throw new Error(result.error || '删除模型失败');
    }
    
    // 3. 如果当前场景受影响，直接在场景树中删除节点
    if (currentSceneId && affectedScenes.has(currentSceneId)) {
        if (window.sceneTreeManager && window.sceneTreeUI) {
            try {
                // 收集当前场景中需要删除的根节点ID（使用过滤后的节点列表）
                const currentSceneNodes = filteredNodes.filter(item => item.sceneId === currentSceneId);
                
                if (currentSceneNodes.length > 0) {
                    
                    // 遍历删除节点（SceneTreeManager.removeNode 会自动处理子节点和3D对象）
                    for (const item of currentSceneNodes) {
                        const node = window.sceneTreeManager.findNodeById(item.rootNodeId);
                        if (node) {
                            window.sceneTreeManager.removeNode(item.rootNodeId);
                        } else {
                            console.warn(`⚠️ 节点已不存在: ${item.rootNodeId}`);
                        }
                    }
                    
                    // 刷新场景树UI
                                window.sceneTreeUI.render();
                            }
            } catch (error) {
                console.warn(`⚠️ 删除场景节点失败:`, error);
            }
        } else {
            console.warn(`⚠️ SceneTreeManager 或 SceneTreeUI 不可用`);
        }
    }
    
    window.showManageMessage('模型及其所有使用节点已删除！', 'success');
    
    // 4. 刷新材质库（删除节点后材质引用计数需要更新）
    try {
        if (window.getMaterialLibraryManager) {
            const materialManager = window.getMaterialLibraryManager(currentProject.id);
            if (materialManager) {
                await materialManager.loadMaterials({ forceRefresh: true });
                console.log('✅ 材质库已刷新，引用计数已更新');
            }
        }
    } catch (error) {
        console.warn('⚠️ 刷新材质库失败:', error);
    }
    
    // 5. 根据modelType刷新对应的UI
    if (modelType === 'node') {
        // 刷新节点库管理列表
        await loadNodeManageList();
        // 刷新节点库侧边栏
        if (window.populateNodeList) {
            await window.populateNodeList();
        }
    } else {
        // 刷新模型库管理列表
        await loadModelManageList();
        // 刷新模型库侧边栏
        await window.refreshModelLibraryPanel();
    }
}

// ============ 模型依赖查看 ============

/**
 * 从节点对象中提取 modelId
 * @param {Object} node - 节点对象
 * @returns {string|null} - 模型ID
 */
function getNodeModelId(node) {
    if (!node || !node.components) return null;
    const modelComponent = node.components.find(c => c.type === 'model');
    return modelComponent ? modelComponent.modelId : null;
}

/**
 * 查看模型依赖信息（第二个定义 - 可能是重复的，但也需要修复）
 */
window.viewModelDependencies = async function(modelId, modelType) {
    try {
        const currentProject = projectManager.getCurrentProject();
        if (!currentProject) {
            window.showManageMessage('未选择项目', 'error');
            return;
        }
        
        // 如果没有传递modelType，尝试从DOM元素获取
        if (!modelType) {
            const modelItem = document.querySelector(`[data-model-id="${modelId}"]`);
            modelType = modelItem?.dataset.modelType || 'model';
        }
        
        // 构建API URL，添加type参数（如果是node类型）
        const typeParam = modelType === 'node' ? '?type=node' : '';
        
        // 1. 获取模型详细信息（包括依赖）
        const modelResponse = await fetch(`/api/projects/${currentProject.id}/models/${modelId}${typeParam}`);
        const modelResult = await modelResponse.json();
        
        if (!modelResult.success) {
            throw new Error(modelResult.error || '获取模型信息失败');
        }
        
        const model = modelResult.data;
        const dependencies = model.usedByNodesDetails || [];
        
        // 2. 如果有依赖，获取每个节点的完整子树信息
        let allAffectedNodes = [];
        let allAffectedModels = new Map(); // modelId -> count
        
        if (dependencies.length > 0) {
            for (const dep of dependencies) {
                try {
                    const subtreeResponse = await fetch(
                        `/api/projects/${currentProject.id}/scenes/${dep.sceneId}/nodes/${dep.nodeId}/subtree`
                    );
                    
                    if (!subtreeResponse.ok) {
                        console.warn(`   ⚠️ 获取子树失败: HTTP ${subtreeResponse.status} - 节点已被删除，跳过`);
                        continue;
                    }
                    
                    const subtreeResult = await subtreeResponse.json();
                    if (subtreeResult.success) {
                        const subtreeData = subtreeResult.data;
                        allAffectedNodes.push({
                            sceneId: dep.sceneId,
                            rootNodeId: dep.nodeId,
                            rootNodeName: dep.nodeName,
                            rootNodePath: dep.nodePath,
                            subtreeNodes: subtreeData.nodes
                        });
                        // 统计受影响的模型（从components中提取modelId）
                        for (const node of subtreeData.nodes) {
                            const nodeModelId = getNodeModelId(node);
                            if (nodeModelId) {
                                const count = allAffectedModels.get(nodeModelId) || 0;
                                allAffectedModels.set(nodeModelId, count + 1);
                            }
                        }
                    } else {
                        console.warn(`   ⚠️ 子树API返回失败: ${subtreeResult.error} - 节点已被删除，跳过`);
                        continue;
                    }
                } catch (error) {
                    console.error(`❌ 获取节点子树失败: ${dep.sceneId}/${dep.nodeId}`, error);
                    continue;
                }
            }
        }
        
        // 3. 显示依赖信息对话框
        await showModelDependenciesDialog(model, allAffectedNodes, allAffectedModels);
    } catch (error) {
        console.error('❌ 查看模型依赖失败:', error);
        window.showManageMessage('查看失败: ' + error.message, 'error');
    }
};

/**
 * 显示模型依赖信息对话框
 */
async function showModelDependenciesDialog(model, affectedNodes, affectedModels) {
    const currentProject = projectManager.getCurrentProject();
    
    // 收集所有需要获取详细信息的模型ID
    const allModelIds = new Set();
    
    // 1. 添加当前模型本身（用于显示）
    allModelIds.add(model.id);
    
    // 2. 添加关联模型（其他模型）
    for (const [modelId] of affectedModels) {
        allModelIds.add(modelId);
    }
    
    // 批量获取所有模型的详细信息
    const modelDetailsMap = new Map();
    if (allModelIds.size > 0) {
        for (const modelId of allModelIds) {
            try {
                const modelResponse = await fetch(`/api/projects/${currentProject.id}/models/${modelId}`);
                const modelResult = await modelResponse.json();
                if (modelResult.success) {
                    modelDetailsMap.set(modelId, {
                        name: modelResult.data.name,
                        format: modelResult.data.format,
                        count: affectedModels.get(modelId) || 0
                    });
                }
            } catch (error) {
                console.error(`获取模型信息失败: ${modelId}`, error);
                modelDetailsMap.set(modelId, {
                    name: modelId,
                    format: 'N/A',
                    count: affectedModels.get(modelId) || 0
                });
            }
        }
    }
    
    return new Promise((resolve) => {
        try {
            // 创建模态窗口
            const modal = document.createElement('div');
            modal.className = 'modal-overlay';
            modal.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0, 0, 0, 0.85);
                backdrop-filter: blur(8px);
                display: flex;
                justify-content: center;
                align-items: center;
                z-index: 999999;
            `;
            
            // ========== 统计数据 ==========
            
            // 1. 统计场景数量
            const totalScenes = new Set(affectedNodes.map(item => item.sceneId)).size;
            
            // 2. 统计将被使用的节点总数（包括根节点和所有子孙节点）
            const totalNodes = affectedNodes.reduce((sum, item) => sum + item.subtreeNodes.length, 0);
            
            // 3. 统计关联模型数量（排除当前模型）
            const otherModelsCount = affectedModels.size - (affectedModels.has(model.id) ? 1 : 0);
            
            // 4. 统计含有模型引用的节点数量
            let totalNodesWithModels = 0;
            let nodesUsingCurrentModel = 0;  // 引用当前模型的节点数
            let nodesUsingOtherModels = 0;   // 引用其他模型的节点数
            
            for (const nodeGroup of affectedNodes) {
                for (const node of nodeGroup.subtreeNodes) {
                    const nodeModelId = getNodeModelId(node);
                    if (nodeModelId) {
                        totalNodesWithModels++;
                        
                        if (nodeModelId === model.id) {
                            nodesUsingCurrentModel++;
                        } else {
                            nodesUsingOtherModels++;
                        }
                    }
                }
            }
            
        let contentHTML = `
            <div class="modal-content delete-confirm-modal" style="max-width: 1200px; width: 95vw; max-height: 90vh; display: flex; flex-direction: column;">
                <div class="modal-header" style="flex-shrink: 0; padding: 15px 20px; display: flex; align-items: center; justify-content: space-between; gap: 20px;">
                    <div style="flex: 1; display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap;">
                        <h3 style="margin: 0; white-space: nowrap;">📊 模型依赖：${model.name}</h3>
                        <div style="display: flex; align-items: center; gap: 6px;">
                            <span style="font-size: 13px;">ℹ️</span>
                            <span style="font-size: 11px; color: rgba(255, 255, 255, 0.65);">显示该模型在各场景中的使用情况</span>
                        </div>
                    </div>
                    <button class="modal-close" onclick="this.closest('.modal-overlay').remove()" style="flex-shrink: 0;">✕</button>
                </div>
                
        `;
        
        // 显示关联模型详情（只显示其他模型）
        const otherAffectedModels = Array.from(affectedModels.entries()).filter(([mid]) => mid !== model.id);
        
        if (otherAffectedModels.length > 0) {
            contentHTML += `
                <!-- 关联模型区域 -->
                <div class="modal-overview" style="flex-shrink: 0; padding: 8px 15px 6px 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
                    <div class="affected-models-section">
                        <div style="font-size: 10px; font-weight: 600; color: rgba(255, 255, 255, 0.6); margin-bottom: 5px; display: flex; align-items: center; gap: 4px;">
                            <span style="font-size: 11px;">🎨</span>
                            <span>子节点引用的其他模型：</span>
                        </div>
                        <div style="display: flex; flex-wrap: wrap; gap: 5px;">
            `;
            
            // 使用更紧凑的横向布局显示关联模型
            for (const [modelId, count] of otherAffectedModels) {
                const modelInfo = modelDetailsMap.get(modelId);
                const modelName = modelInfo ? modelInfo.name : modelId;
                const modelFormat = modelInfo ? modelInfo.format.toUpperCase() : 'N/A';
                
                contentHTML += `
                            <div style="background: rgba(102, 126, 234, 0.08); border: 1px solid rgba(102, 126, 234, 0.18); border-radius: 4px; padding: 4px 8px; display: inline-flex; align-items: center; gap: 4px;">
                                <span style="color: rgba(255, 255, 255, 0.9); font-size: 11px; font-weight: 500;">${modelName}</span>
                                <span style="color: rgba(255, 255, 255, 0.35); font-size: 9px;">${modelFormat}</span>
                                <span style="background: rgba(102, 126, 234, 0.25); padding: 1px 5px; border-radius: 2px; color: #667eea; font-weight: 600; font-size: 9px;">${count}次</span>
                            </div>
                `;
            }
            
            contentHTML += `
                        </div>
                    </div>
                </div>
            `;
        }
        
        // 开始节点详情区域
        contentHTML += `
                <!-- 可滚动的节点详情区域 -->
                <div class="modal-body" style="flex: 1; overflow-y: auto; padding: 8px 15px 12px 15px;">
        `;
        
        if (affectedNodes.length > 0) {
            // 获取场景信息
            const scenes = projectManager.getScenes();
            const sceneMap = new Map(scenes.map(s => [s.id, s.name]));
            
            // 按场景分组节点
            const nodesByScene = new Map();
            for (const item of affectedNodes) {
                if (!nodesByScene.has(item.sceneId)) {
                    nodesByScene.set(item.sceneId, []);
                }
                nodesByScene.get(item.sceneId).push(item);
            }
            
            contentHTML += `
                <div class="affected-nodes-section">
            `;
            
            // 遍历每个场景
            let sceneIndex = 0;
            for (const [sceneId, sceneNodes] of nodesByScene) {
                const sceneName = sceneMap.get(sceneId) || '未命名场景';
                const sceneNodeCount = sceneNodes.reduce((sum, item) => sum + item.subtreeNodes.length, 0);
                
                // 计算该场景引用的资源数（去重）
                const sceneModels = new Set();
                for (const item of sceneNodes) {
                    for (const node of item.subtreeNodes) {
                        const nodeModelId = getNodeModelId(node);
                        if (nodeModelId) {
                            sceneModels.add(nodeModelId);
                        }
                    }
                }
                const sceneModelCount = sceneModels.size;
                
                const collapseId = `scene-collapse-${sceneIndex}`;
                
                contentHTML += `
                    <div class="scene-group" style="margin-bottom: 8px;">
                        <!-- 场景展开栏 -->
                        <div onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.querySelector('.collapse-icon').textContent = this.nextElementSibling.style.display === 'none' ? '▶' : '▼';" 
                             style="background: linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(102, 126, 234, 0.08) 100%); border: 1px solid rgba(102, 126, 234, 0.3); border-radius: 5px; padding: 8px 12px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: space-between;"
                             onmouseenter="this.style.background='linear-gradient(135deg, rgba(102, 126, 234, 0.2) 0%, rgba(102, 126, 234, 0.12) 100%)'; this.style.borderColor='rgba(102, 126, 234, 0.4)';"
                             onmouseleave="this.style.background='linear-gradient(135deg, rgba(102, 126, 234, 0.15) 0%, rgba(102, 126, 234, 0.08) 100%)'; this.style.borderColor='rgba(102, 126, 234, 0.3)';">
                            <div style="display: flex; align-items: center; gap: 8px;">
                                <span class="collapse-icon" style="font-size: 10px; color: rgba(255, 255, 255, 0.6); transition: transform 0.2s;">▼</span>
                                <span style="font-size: 13px;">🏞️</span>
                                <span style="font-weight: 600; color: rgba(255, 255, 255, 0.95); font-size: 12px;">${sceneName}</span>
                                <span style="color: rgba(255, 255, 255, 0.4); font-size: 10px;">(${sceneId})</span>
                            </div>
                            <div style="display: flex; align-items: center; gap: 6px;">
                                <span style="background: rgba(102, 126, 234, 0.3); padding: 3px 8px; border-radius: 4px; font-size: 10px; color: #667eea; font-weight: 600;">
                                    ${sceneNodes.length}个根节点
                                </span>
                                <span style="background: rgba(102, 126, 234, 0.3); padding: 3px 8px; border-radius: 4px; font-size: 10px; color: #667eea; font-weight: 600;">
                                    ${sceneNodeCount}个节点
                                </span>
                                ${sceneModelCount > 0 ? `<span style="background: rgba(253, 203, 110, 0.3); padding: 3px 8px; border-radius: 4px; font-size: 10px; color: #fdcb6e; font-weight: 600;">
                                    ${sceneModelCount}个资源
                                </span>` : ''}
                            </div>
                        </div>
                        
                        <!-- 该场景下的节点路径列表（可折叠） -->
                        <div id="${collapseId}" style="padding: 8px 0 0 0;">
                `;
                
                // 显示节点路径
                for (const item of sceneNodes) {
                    // 计算该子树使用当前模型的节点数
                    const nodesUsingModel = item.subtreeNodes.filter(n => getNodeModelId(n) === model.id).length;
                    
                    // 显示根节点路径
                    contentHTML += `
                        <div class="node-path-item" style="background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 5px; padding: 8px 10px; margin-bottom: 6px;">
                            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
                                <span style="font-size: 11px;">📍</span>
                                <span style="color: rgba(255, 255, 255, 0.95); font-size: 11px; font-weight: 600;">${item.rootNodeName}</span>
                                <span style="background: rgba(253, 203, 110, 0.25); padding: 1px 5px; border-radius: 2px; color: #fdcb6e; font-weight: 600; font-size: 9px;">${nodesUsingModel}个节点使用</span>
                            </div>
                            <div style="color: rgba(255, 255, 255, 0.5); font-size: 10px; font-family: 'Consolas', 'Monaco', monospace;">
                                路径: ${item.rootNodePath || '/'}
                            </div>
                    `;
                    
                    // 显示子节点中使用该模型的节点
                    const modelNodes = item.subtreeNodes.filter(n => getNodeModelId(n) === model.id);
                    if (modelNodes.length > 0) {
                        contentHTML += `
                            <div style="margin-top: 6px; padding-left: 10px; border-left: 2px solid rgba(102, 126, 234, 0.3);">
                        `;
                        
                        for (const node of modelNodes) {
                            const depth = node.depth || 0;
                            const indent = '  '.repeat(depth);
                            
                            contentHTML += `
                                <div style="padding: 2px 0; font-size: 10px; color: rgba(255, 255, 255, 0.7); font-family: 'Consolas', 'Monaco', monospace;">
                                    <span style="color: rgba(255, 255, 255, 0.3);">${indent}${depth > 0 ? '└─ ' : ''}</span>
                                    <span>${node.name || node.nodeName}</span>
                                    <span style="color: rgba(102, 126, 234, 0.6); margin-left: 4px;">→ 🎨</span>
                                </div>
                            `;
                        }
                        
                        contentHTML += `
                            </div>
                        `;
                    }
                    
                    contentHTML += `
                        </div>
                    `;
                }
                
                // 关闭场景下的节点列表
                contentHTML += `
                        </div>
                    </div>
                `;
                
                sceneIndex++;
            }
            
            // 关闭 affected-nodes-section
            contentHTML += `</div>`;
        } else {
            contentHTML += `
                <div style="background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; padding: 20px; text-align: center; color: rgba(255, 255, 255, 0.5);">
                    ✓ 此模型未被任何场景节点使用
                </div>
            `;
        }
        
        contentHTML += `
                </div>
                
                <div class="modal-footer" style="flex-shrink: 0; display: flex; gap: 10px; justify-content: flex-end; padding: 15px 20px; border-top: 1px solid rgba(255,255,255,0.1);">
                    <button class="modal-btn modal-btn-primary" onclick="this.closest('.modal-overlay').remove()" style="min-width: 100px;">
                        关闭
                    </button>
                </div>
            </div>
        `;
        modal.innerHTML = contentHTML;
        document.body.appendChild(modal);
        
        // 点击背景关闭
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove();
                resolve(false);
            }
        });
        
        } catch (error) {
            console.error('❌ showModelDependenciesDialog 执行出错:', error);
            console.error('   - 错误堆栈:', error.stack);
            resolve(false);
        }
    });
}

// ============ 模型结构查看 ============

/**
 * 解析模型层级结构
 */
function parseModelStructure(object3D, depth = 0) {
    const node = {
        name: fixStringEncoding(object3D.name || '(unnamed)'),
        type: object3D.type,
        depth: depth,
        children: [],
        info: {}
    };
    
    // 收集节点信息
    if (object3D.isMesh) {
        const positionCount = object3D.geometry.attributes.position?.count || 0;
        let facesCount = 0;
        
        // 计算面数
        if (object3D.geometry.index) {
            // 有索引的几何体
            facesCount = object3D.geometry.index.count / 3;
        } else if (positionCount > 0) {
            // 无索引的几何体，每3个顶点组成一个面
            facesCount = positionCount / 3;
        }
        
        node.info.geometry = {
            type: object3D.geometry.type,
            vertices: positionCount,
            faces: facesCount
        };
        
        // 材质信息
        const materials = Array.isArray(object3D.material) ? object3D.material : [object3D.material];
        node.info.materials = materials.map(mat => ({
            type: mat.type,
            name: fixStringEncoding(mat.name || '(unnamed)')
        }));
    }
    
    // 位置信息
    node.info.transform = {
        position: {
            x: object3D.position.x.toFixed(3),
            y: object3D.position.y.toFixed(3),
            z: object3D.position.z.toFixed(3)
        },
        rotation: {
            x: (object3D.rotation.x * 180 / Math.PI).toFixed(1),
            y: (object3D.rotation.y * 180 / Math.PI).toFixed(1),
            z: (object3D.rotation.z * 180 / Math.PI).toFixed(1)
        },
        scale: {
            x: object3D.scale.x.toFixed(3),
            y: object3D.scale.y.toFixed(3),
            z: object3D.scale.z.toFixed(3)
        }
    };
    
    // 递归处理子节点
    if (object3D.children && object3D.children.length > 0) {
        node.children = object3D.children.map(child => parseModelStructure(child, depth + 1));
    }
    
    return node;
}

/**
 * 显示模型结构弹窗
 */
function showModelStructureModal(model, structure) {
    // 创建弹窗
    let modal = document.getElementById('model-structure-modal');
    if (!modal) {
        modal = document.createElement('div');
        modal.id = 'model-structure-modal';
        modal.className = 'modal';
        document.body.appendChild(modal);
    }
    
    // 统计信息
    const stats = calculateStructureStats(structure);
    
    // 生成树形HTML
    const treeHTML = generateStructureTreeHTML(structure);
    
    modal.innerHTML = `
        <div class="modal-content" style="width: 90vw; max-width: 1400px; min-width: 800px; max-height: 90vh; display: flex; flex-direction: column; position: relative; padding: 0;">
            <!-- 固定的头部区域，包含标题和关闭按钮 -->
            <div style="position: relative; flex-shrink: 0; padding: 15px 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); background: rgba(30, 30, 30, 0.95);">
                <h3 style="margin: 0; color: rgba(255, 255, 255, 0.9); font-size: 16px; padding-right: 40px;">模型结构</h3>
                <button class="modal-close" onclick="closeModelStructureModal()" style="position: absolute; top: 10px; right: 15px; z-index: 10; background: rgba(255,255,255,0.1); border: none; color: #fff; font-size: 24px; width: 32px; height: 32px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s;" onmouseenter="this.style.background='rgba(255,255,255,0.2)'" onmouseleave="this.style.background='rgba(255,255,255,0.1)'">×</button>
            </div>
            
            <!-- 可滚动的内容区域 -->
            <div class="modal-body" style="flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0; padding: 20px;">
                <!-- 统计信息 - 简洁版 -->
                <div style="background: rgba(102, 126, 234, 0.08); border: 1px solid rgba(102, 126, 234, 0.2); border-radius: 8px; padding: 15px; margin-bottom: 20px;">
                    <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; font-size: 13px;">
                        <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: rgba(0, 0, 0, 0.15); border-radius: 5px;">
                            <span style="color: #fff; font-size: 12px;">节点</span>
                            <span style="color: #667eea; font-weight: 700; font-size: 15px;">${stats.totalNodes}</span>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: rgba(0, 0, 0, 0.15); border-radius: 5px;">
                            <span style="color: #fff; font-size: 12px;">网格</span>
                            <span style="color: #667eea; font-weight: 700; font-size: 15px;">${stats.meshes}</span>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: rgba(0, 0, 0, 0.15); border-radius: 5px;">
                            <span style="color: #fff; font-size: 12px;">顶点</span>
                            <span style="color: #667eea; font-weight: 700; font-size: 15px;">${stats.totalVertices.toLocaleString()}</span>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: rgba(0, 0, 0, 0.15); border-radius: 5px;">
                            <span style="color: #fff; font-size: 12px;">面</span>
                            <span style="color: #667eea; font-weight: 700; font-size: 15px;">${stats.totalFaces.toLocaleString()}</span>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: rgba(0, 0, 0, 0.15); border-radius: 5px;">
                            <span style="color: #fff; font-size: 12px;">材质</span>
                            <span style="color: #667eea; font-weight: 700; font-size: 15px;">${stats.materials}</span>
                        </div>
                        <div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: rgba(0, 0, 0, 0.15); border-radius: 5px;">
                            <span style="color: #fff; font-size: 12px;">深度</span>
                            <span style="color: #667eea; font-weight: 700; font-size: 15px;">${stats.maxDepth}</span>
                        </div>
                    </div>
                </div>
                
                <!-- 层级树 -->
                <div style="background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; padding: 20px; font-family: 'Consolas', 'Monaco', 'Courier New', monospace; font-size: 12px; line-height: 1.6;">
                    ${treeHTML}
                </div>
            </div>
        </div>
    `;
    
    modal.classList.add('active');
}

/**
 * 关闭模型结构弹窗
 */
window.closeModelStructureModal = function() {
    const modal = document.getElementById('model-structure-modal');
    if (modal) {
        modal.classList.remove('active');
    }
};

/**
 * 计算结构统计信息
 */
function calculateStructureStats(node) {
    let stats = {
        totalNodes: 0,
        meshes: 0,
        totalVertices: 0,
        totalFaces: 0,
        materials: 0,
        maxDepth: 0
    };
    
    function traverse(n, depth) {
        stats.totalNodes++;
        stats.maxDepth = Math.max(stats.maxDepth, depth);
        
        if (n.info.geometry) {
            stats.meshes++;
            stats.totalVertices += n.info.geometry.vertices || 0;
            stats.totalFaces += n.info.geometry.faces || 0;
        }
        
        if (n.info.materials) {
            stats.materials += n.info.materials.length;
        }
        
        n.children.forEach(child => traverse(child, depth + 1));
    }
    
    traverse(node, 0);
    return stats;
}

/**
 * 生成层级树HTML - 改进版，更清晰的层级关系
 */
function generateStructureTreeHTML(node, isLast = true, prefix = '', isRoot = true) {
    let html = '';
    
    // 节点图标
    let icon = '📦';
    if (node.type === 'Mesh') icon = '🎨';
    else if (node.type === 'Group') icon = '📁';
    else if (node.type === 'Scene') icon = '🌍';
    else if (node.type === 'Bone') icon = '🦴';
    else if (node.type === 'SkinnedMesh') icon = '👤';
    
    // 深度颜色（越深颜色越淡）
    const depthAlpha = Math.max(0.95 - node.depth * 0.08, 0.5);
    
    // 树形连接线 - 使用更粗的Unicode字符
    let treePrefix = '';
    if (!isRoot) {
        treePrefix = prefix + (isLast ? '└── ' : '├── ');
    }
    
    // 背景色 - 根据深度
    const bgColor = `rgba(${100 + node.depth * 10}, ${120 + node.depth * 8}, ${234 - node.depth * 5}, ${0.04 + node.depth * 0.02})`;
    
    // 节点容器 - 增加padding和背景
    html += `<div style="
        margin: 3px 0; 
        padding: 6px 10px; 
        border-radius: 5px; 
        background: ${bgColor};
        border-left: 2px solid rgba(102, 126, 234, ${0.2 + node.depth * 0.05});
        transition: all 0.2s;
    " onmouseenter="this.style.background='rgba(102, 126, 234, 0.15)'" onmouseleave="this.style.background='${bgColor}'">`;
    
    // 树形前缀和图标
    html += `<div style="display: flex; align-items: center; gap: 8px;">`;
    html += `<span style="color: rgba(255, 255, 255, 0.35); font-weight: bold; font-size: 13px; font-family: monospace; min-width: ${node.depth * 20}px;">${treePrefix}</span>`;
    html += `<span style="font-size: 16px; flex-shrink: 0;">${icon}</span>`;
    
    // 节点信息容器
    html += `<div style="flex: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 8px;">`;
    
    // 节点名称
    html += `<span style="font-weight: 600; color: rgba(255, 255, 255, ${depthAlpha}); font-size: 13px;">${node.name}</span>`;
    
    // 节点类型标签
    html += `<span style="
        background: rgba(102, 126, 234, 0.2); 
        padding: 2px 8px; 
        border-radius: 4px; 
        color: rgba(255, 255, 255, 0.6); 
        font-size: 10px;
        font-weight: 500;
        border: 1px solid rgba(102, 126, 234, 0.3);
    ">${node.type}</span>`;
    
    // 几何信息（如果是网格）
    if (node.info.geometry) {
        html += `<span style="
            color: rgba(76, 175, 80, 0.9); 
            font-size: 11px;
            background: rgba(76, 175, 80, 0.1);
            padding: 2px 8px;
            border-radius: 4px;
            border: 1px solid rgba(76, 175, 80, 0.2);
        ">`;
        html += `📊 ${node.info.geometry.vertices.toLocaleString()}顶点 · ${Math.floor(node.info.geometry.faces).toLocaleString()}面`;
        html += `</span>`;
    }
    
    html += `</div></div>`; // 关闭节点信息容器和flex容器
    
    // 材质信息
    if (node.info.materials && node.info.materials.length > 0) {
        html += `<div style="margin-top: 6px; padding-left: ${(node.depth + 1) * 20 + 30}px;">`;
        node.info.materials.forEach((mat, idx) => {
            html += `<div style="
                margin: 3px 0; 
                padding: 4px 8px;
                background: rgba(253, 203, 110, 0.08);
                border-left: 2px solid rgba(253, 203, 110, 0.3);
                border-radius: 3px;
                font-size: 11px;
                display: flex;
                align-items: center;
                gap: 6px;
            ">`;
            html += `<span style="font-size: 12px;">🎨</span>`;
            html += `<span style="color: rgba(255, 255, 255, 0.7);">材质:</span>`;
            html += `<span style="color: rgba(253, 203, 110, 0.9); font-weight: 500;">${mat.name}</span>`;
            html += `<span style="
                background: rgba(253, 203, 110, 0.15);
                padding: 1px 6px;
                border-radius: 3px;
                color: rgba(255, 255, 255, 0.5);
                font-size: 9px;
            ">${mat.type}</span>`;
            html += `</div>`;
        });
        html += `</div>`;
    }
    
    html += `</div>`; // 关闭节点容器
    
    // 递归渲染子节点
    if (node.children.length > 0) {
        const childPrefix = prefix + (isLast ? '    ' : '│   ');
        html += `<div style="margin-left: 16px; border-left: 2px solid rgba(102, 126, 234, 0.15); padding-left: 4px;">`;
        node.children.forEach((child, idx) => {
            const childIsLast = idx === node.children.length - 1;
            html += generateStructureTreeHTML(child, childIsLast, childPrefix, false);
        });
        html += `</div>`;
    }
    
    return html;
}

// ============ 导出模块 ============

export default {
    initializeModelLibrary,
    openModelLibrary: window.openModelLibrary,
    closeModelLibrary: window.closeModelLibrary,
    refreshModelLibraryPanel: window.refreshModelLibraryPanel,
    openModelLibraryManage: window.openModelLibraryManage,
    closeModelLibraryManage: window.closeModelLibraryManage,
    uploadModelDialog: window.uploadModelDialog,
    editModelInfo: window.editModelInfo,
    deleteProjectModel: window.deleteProjectModel,
    updateModelNodeReferences: updateModelNodeReferences,
    viewModelStructure: window.viewModelStructure,
    viewModelDependencies: window.viewModelDependencies
};

// 将updateModelNodeReferences绑定到全局，供其他模块使用
window.updateModelNodeReferences = updateModelNodeReferences;

// 将loadModelManageList绑定到全局，供其他模块使用
window.loadModelManageList = loadModelManageList;

// ============ 模型加载确认对话框 ============

/**
 * 显示模型加载确认对话框
 * @param {string} modelName - 模型名称
 * @returns {Promise<boolean>} - 用户是否确认加载
 * @deprecated 已废弃：不再使用确认对话框，直接点击下载按钮开始下载
 */
/* 
function showModelLoadConfirmDialog(modelName) {
    return new Promise((resolve) => {
        // 创建对话框容器
        const dialog = document.createElement('div');
        dialog.className = 'model-load-confirm-dialog';
        dialog.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            backdrop-filter: blur(10px);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 100000;
            animation: fadeIn 0.2s ease-out;
        `;
        
        // 创建对话框内容
        const dialogContent = document.createElement('div');
        dialogContent.style.cssText = `
            background: linear-gradient(135deg, rgba(30, 30, 30, 0.95), rgba(40, 40, 50, 0.95));
            border: 2px solid rgba(102, 126, 234, 0.5);
            border-radius: 12px;
            padding: 30px;
            max-width: 400px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
            animation: slideIn 0.3s ease-out;
        `;
        
        dialogContent.innerHTML = `
            <div style="text-align: center;">
                <div style="font-size: 48px; margin-bottom: 20px;">📦</div>
                <h3 style="color: white; margin: 0 0 15px 0; font-size: 18px;">模型未加载</h3>
                <p style="color: rgba(255, 255, 255, 0.8); margin: 0 0 25px 0; line-height: 1.6;">
                    模型 <strong style="color: #667eea;">${modelName}</strong> 还未加载到缓存中。
                    <br>
                    是否现在加载该模型？
                </p>
                <div style="display: flex; gap: 12px; justify-content: center;">
                    <button id="confirm-load-btn" style="
                        padding: 10px 24px;
                        background: linear-gradient(135deg, #667eea, #764ba2);
                        color: white;
                        border: none;
                        border-radius: 6px;
                        cursor: pointer;
                        font-size: 14px;
                        font-weight: 600;
                        transition: all 0.3s;
                    ">
                        ✓ 加载模型
                    </button>
                    <button id="cancel-load-btn" style="
                        padding: 10px 24px;
                        background: rgba(255, 255, 255, 0.1);
                        color: rgba(255, 255, 255, 0.8);
                        border: 1px solid rgba(255, 255, 255, 0.2);
                        border-radius: 6px;
                        cursor: pointer;
                        font-size: 14px;
                        font-weight: 600;
                        transition: all 0.3s;
                    ">
                        ✗ 取消
                    </button>
                </div>
            </div>
        `;
        
        dialog.appendChild(dialogContent);
        document.body.appendChild(dialog);
        
        // 添加动画样式
        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; }
                to { opacity: 1; }
            }
            @keyframes slideIn {
                from { 
                    opacity: 0; 
                    transform: translateY(-20px) scale(0.95);
                }
                to { 
                    opacity: 1; 
                    transform: translateY(0) scale(1);
                }
            }
            #confirm-load-btn:hover {
                transform: translateY(-2px);
                box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
            }
            #cancel-load-btn:hover {
                background: rgba(255, 255, 255, 0.15);
                border-color: rgba(255, 255, 255, 0.3);
            }
        `;
        document.head.appendChild(style);
        
        // 绑定事件
        const confirmBtn = dialogContent.querySelector('#confirm-load-btn');
        const cancelBtn = dialogContent.querySelector('#cancel-load-btn');
        
        const closeDialog = (result) => {
            dialog.style.animation = 'fadeIn 0.2s ease-out reverse';
            setTimeout(() => {
                dialog.remove();
                style.remove();
                resolve(result);
            }, 200);
        };
        
        confirmBtn.addEventListener('click', () => closeDialog(true));
        cancelBtn.addEventListener('click', () => closeDialog(false));
        
        // 点击背景关闭
        dialog.addEventListener('click', (e) => {
            if (e.target === dialog) {
                closeDialog(false);
            }
        });
        
        // ESC键关闭
        const handleEscape = (e) => {
            if (e.key === 'Escape') {
                closeDialog(false);
                document.removeEventListener('keydown', handleEscape);
            }
        };
        document.addEventListener('keydown', handleEscape);
    });
}
*/

/**
 * 加载模型到缓存
 * @param {string} modelId - 模型ID
 * @returns {Promise<void>}
 * @deprecated 已废弃：不再使用确认对话框，直接点击下载按钮开始下载
 */
/*
async function loadModelToCache(modelId) {
    if (!window.modelLoader) {
        throw new Error('模型加载器未初始化');
    }
    
    // 调用 modelLoader.loadModelById 会自动缓存模型
    await window.modelLoader.loadModelById(modelId, true);
}
*/

// ============ 模型下载管理 ============

/**
 * 创建支持 AbortSignal 和进度回调的模型加载器函数
 * @param {string} modelPath - 模型路径
 * @param {AbortSignal} signal - 取消信号
 * @param {Function} onProgress - 进度回调
 * @returns {Promise<THREE.Object3D>}
 */
async function createModelLoader(modelPath, signal, onProgress) {
    return new Promise((resolve, reject) => {
        // 检测文件格式
        const ext = modelPath.split('.').pop().toLowerCase();
        const isGLTF = ext === 'gltf' || ext === 'glb';
        const isFBX = ext === 'fbx';
        
        let loader;
        if (isGLTF) {
            loader = new GLTFLoader();
        } else if (isFBX) {
            loader = new FBXLoader();
        } else {
            reject(new Error('不支持的模型格式: ' + ext));
            return;
        }
        
        // 构建模型URL
        const normalizedPath = modelPath.startsWith('/') ? modelPath : '/' + modelPath;
        const modelUrl = `http://localhost:7283${normalizedPath}`;
        
        // 监听取消信号
        if (signal) {
            signal.addEventListener('abort', () => {
                reject(new Error('下载已取消'));
            });
        }
        
        // 加载模型
        loader.load(
            modelUrl,
            (loadedObject) => {
                // GLTF返回 {scene: ...}，FBX直接返回对象
                const object3D = isGLTF ? loadedObject.scene : loadedObject;
                resolve(object3D);
            },
            (progressEvent) => {
                if (onProgress && progressEvent.lengthComputable) {
                    onProgress(progressEvent.loaded, progressEvent.total);
                }
            },
            (error) => {
                reject(error);
            }
        );
    });
}

/**
 * 开始下载单个模型
 * @param {string} modelId - 模型ID
 */
async function startModelDownload(modelId) {
    try {
        // 从所有模型中查找目标模型（包括模型库和节点库）
        let model = window._allModels?.find(m => m.id === modelId);
        
        // 如果在模型库中找不到，尝试从节点库中查找
        if (!model) {
            model = window._allNodes?.find(m => m.id === modelId);
        }
        
        if (!model) {
            console.error('❌ 未找到模型:', modelId);
            return;
        }
        
        // 使用 modelCacheManager 的下载功能        
        await modelCacheManager.downloadModel(model, createModelLoader);
        
    } catch (error) {
        if (error.message === '下载已取消') {
            // 下载已取消，不需要提示
        } else {
            console.error(`❌ 模型下载失败: ${modelId}`, error);
        }
    }
}

/**
 * 取消单个模型的下载
 * @param {string} modelId - 模型ID
 */
function cancelModelDownload(modelId) {
    modelCacheManager.cancelDownload(modelId);
}

/**
 * 批量下载所有未下载的模型
 */
async function downloadAllModels() {
    // 判断当前激活的Tab，决定下载模型库还是节点库的模型
    const modelLibraryTab = document.getElementById('model-library-tab');
    const nodeLibraryTab = document.getElementById('node-library-tab');
    
    let allModels;
    let modelType = '模型';
    
    if (nodeLibraryTab && nodeLibraryTab.classList.contains('active')) {
        // 节点库Tab激活，下载节点库的模型
        allModels = window._allNodes;
        modelType = '节点模型';
    } else {
        // 模型库Tab激活（默认），下载模型库的模型
        allModels = window._allModels;
        modelType = '模型';
    }
    
    if (!allModels || allModels.length === 0) {
        console.log(`没有可下载的${modelType}`);
        return;
    }
    
    // 统计未下载的模型数量
    const unloadedModels = allModels.filter(
        model => !modelCacheManager.isModelLoaded(model.id)
    );
    
    if (unloadedModels.length === 0) {
        console.log(`所有${modelType}已下载`);
        return;
    }
    
    // 🎯 直接开始下载，不需要确认
    
    try {
        // 更新批量下载按钮状态
        updateBatchDownloadButton(true);
        
        const results = await modelCacheManager.downloadAllModels(
            allModels,
            createModelLoader
        );
        
        // 显示结果提示
        if (results.success > 0 || results.failed > 0 || results.cancelled > 0) {
            console.log(`${modelType}下载完成！成功: ${results.success}, 失败: ${results.failed}, 取消: ${results.cancelled}`);
        }
        
    } catch (error) {
        console.error(`❌ 批量下载${modelType}失败:`, error);
    } finally {
        // 恢复批量下载按钮状态
        updateBatchDownloadButton(false);
    }
}

/**
 * 取消所有下载任务
 */
function cancelAllDownloads() {
    modelCacheManager.cancelAllDownloads();
    updateBatchDownloadButton(false);
}

/**
 * 更新批量下载按钮状态
 * @param {boolean} isDownloading - 是否正在下载
 */
function updateBatchDownloadButton(isDownloading) {
    const downloadAllBtn = document.getElementById('download-all-models-btn');
    if (!downloadAllBtn) return;
    
    if (isDownloading) {
        downloadAllBtn.textContent = '⏳';
        downloadAllBtn.title = '下载中...';
        downloadAllBtn.onclick = null; // 移除取消功能
        downloadAllBtn.disabled = true; // 禁用按钮
        downloadAllBtn.classList.add('downloading');
        downloadAllBtn.style.cursor = 'default'; // 鼠标样式改为默认
    } else {
        downloadAllBtn.textContent = '📥';
        downloadAllBtn.title = '下载所有未下载的模型到缓存';
        downloadAllBtn.onclick = downloadAllModels;
        downloadAllBtn.disabled = false; // 启用按钮
        downloadAllBtn.classList.remove('downloading');
        downloadAllBtn.style.cursor = 'pointer'; // 恢复鼠标样式
    }
}

/**
 * 格式化文件大小
 * @param {number} bytes - 字节数
 * @returns {string}
 */
function formatFileSize(bytes) {
    if (bytes === 0) return '0 B';
    if (!bytes) return '';
    
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    
    return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}

// 暴露下载函数到全局
window.downloadAllModels = downloadAllModels;

